Bug 1109873 - Move hit-testing data into HitTestingTreeNode. r=botond
authorKartikaya Gupta <kgupta@mozilla.com>
Thu, 08 Jan 2015 09:40:01 -0500
changeset 222780 aa2fa9528e520bb8199d1cf11b5911397f1c489b
parent 222779 83aa477d175baf0f4628f4151ce0c827ef7d5fd7
child 222781 7d4c6b7bf0d547b6a5bde43617dbc6bd5cc296ec
push id10716
push userkwierso@gmail.com
push dateFri, 09 Jan 2015 01:17:28 +0000
treeherderfx-team@0f98d51a4a49 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbotond
bugs1109873
milestone37.0a1
Bug 1109873 - Move hit-testing data into HitTestingTreeNode. r=botond
gfx/layers/apz/src/APZCTreeManager.cpp
gfx/layers/apz/src/AsyncPanZoomController.h
gfx/layers/apz/src/HitTestingTreeNode.cpp
gfx/layers/apz/src/HitTestingTreeNode.h
gfx/tests/gtest/TestAsyncPanZoomController.cpp
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -385,27 +385,23 @@ 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->Apzc()->Matches(guid));
 
-    nsIntRegion unobscured;
     if (!gfxPrefs::LayoutEventRegionsEnabled()) {
-      unobscured = ComputeTouchSensitiveRegion(state->mController, aMetrics, aObscured);
+      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());
     }
-    // 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);
+    apzc->SetAncestorTransform(aAncestorTransform);
 
     PrintAPZCInfo(aLayer, apzc);
 
     // Bind the APZC instance into the tree of APZCs
     AttachNodeToTree(node, aParent, aNextSibling);
 
     // For testing, log the parent scroll id of every APZC that has a
     // parent. This allows test code to reconstruct the APZC tree.
@@ -437,45 +433,34 @@ 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.
-    // FIXME: Combining this hit test region to the existing hit test region has a bit
-    // of a problem, because it assumes the z-index of this new region is the same as
-    // the z-index of the old region (from the previous layer with the same scrollid)
-    // when in fact that may not be the case.
-    // 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);
+      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());
     }
   }
 
   return node;
 }
 
 HitTestingTreeNode*
 APZCTreeManager::UpdatePanZoomControllerTree(TreeBuildingState& aState,
@@ -508,31 +493,32 @@ APZCTreeManager::UpdatePanZoomController
   Matrix4x4 transform = aLayer.GetTransform();
   Matrix4x4 ancestorTransform = transform;
   if (!apzc) {
     ancestorTransform = ancestorTransform * aAncestorTransform;
   }
 
   uint64_t childLayersId = (aLayer.AsRefLayer() ? aLayer.AsRefLayer()->GetReferentId() : aLayersId);
 
-  nsIntRegion obscured;
+  nsIntRegion obscuredByUncles;
   if (aLayersId == childLayersId) {
-    // If the child layer is in the same process, transform
-    // aObscured from aLayer's ParentLayerPixels to aLayer's LayerPixels,
-    // which are the children layers' ParentLayerPixels.
+    // 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.
-    obscured = aObscured;
-    obscured.Transform(To3DMatrix(transform).Inverse());
+    obscuredByUncles = aObscured;
   }
 
   // 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.
@@ -548,16 +534,19 @@ APZCTreeManager::UpdatePanZoomController
   // 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());
   for (LayerMetricsWrapper child = aLayer.GetLastChild(); child; child = child.GetPrevSibling()) {
     gfx::TreeAutoIndent indent(mApzcTreeLog);
     next = UpdatePanZoomControllerTree(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.
@@ -580,73 +569,84 @@ APZCTreeManager::UpdatePanZoomController
     // 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.
-    //
-    // Also at this point in the code the |obscured| region includes the hit
-    // regions of children of |aLayer| as well as the hit regions of |aLayer|'s
-    // younger uncles (i.e. the next-sibling chains of |aLayer|'s ancestors).
-    // When we compute the unobscured regions below, we subtract off the
-    // |obscured| region, but it would also be ok to do this before the above
-    // loop. At that point |obscured| would only have the uncles' hit regions
-    // and not the children. The reason this is ok is again because of the way
-    // we do hit-testing (where the deepest APZC is used) it doesn't matter if
-    // we count the children as obscuring the parent or not.
-
-    EventRegions unobscured;
-    unobscured.Sub(aLayer.GetEventRegions(), obscured);
-    APZCTM_LOG("Picking up unobscured hit region %s from layer %p\n", Stringify(unobscured).c_str(), aLayer.GetLayer());
 
     // 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 hit region for this layer, and then ...
-    subtreeEventRegions.OrWith(unobscured);
-    // ... transform it up to the parent layer's coordinate space.
-    subtreeEventRegions.Transform(To3DMatrix(aLayer.GetTransform()));
-    if (aLayer.GetClipRect()) {
-      subtreeEventRegions.AndWith(*aLayer.GetClipRect());
-    }
-
-    APZCTM_LOG("After processing layer %p the subtree hit region is %s\n", aLayer.GetLayer(), Stringify(subtreeEventRegions).c_str());
+    // ... 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 (apzc) {
-      APZCTM_LOG("Adding region %s to visible region of APZC %p\n", Stringify(subtreeEventRegions).c_str(), apzc);
+    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);
-        subtreeEventRegions.AndWith(ParentLayerIntRect::ToUntyped(
+        clipRegion.AndWith(ParentLayerIntRect::ToUntyped(
             RoundedIn(touchSensitiveRegion
                     * aLayer.Metrics().GetDevPixelsPerCSSPixel()
                     * parentCumulativeResolution)));
       }
-      apzc->AddHitTestRegions(subtreeEventRegions);
+
+      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.
@@ -1328,23 +1328,29 @@ 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 (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.
+    if (!gfxPrefs::LayoutEventRegionsEnabled() && target == node->Apzc()
+        && node->GetPrevSibling() && target == node->GetPrevSibling()->Apzc()) {
+      // 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 that we need to keep looping through
+      // to see if there are any other non-scrollinfo siblings that have
+      // children that match this event. If so, they should take priority.
+      // Once we turn on event-regions and ignore scrollinfo layers, this
+      // will become unnecessary.
       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;
     }
   }
@@ -1480,17 +1486,17 @@ APZCTreeManager::GetAPZCAtPoint(HitTesti
   // 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 = 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",
+  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);
 
   // 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(apzc->GetCurrentAsyncTransform()).Inverse();
@@ -1500,43 +1506,51 @@ APZCTreeManager::GetAPZCAtPoint(HitTesti
            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 (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.
+      if (!gfxPrefs::LayoutEventRegionsEnabled() && match == aNode->Apzc()
+          && aNode->GetPrevSibling() && match == aNode->GetPrevSibling()->Apzc()) {
+        // 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 that we need to keep looping through
+        // to see if there are any other non-scrollinfo siblings that have
+        // children that match this event. If so, they should take priority.
+        // Once we turn on event-regions and ignore scrollinfo layers, this
+        // will become unnecessary.
         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 (apzc->HitRegionContains(point)) {
-      APZCTM_LOG("Successfully matched untransformed point %f %f to visible region for APZC %p\n",
-                 hitTestPointForThisLayer.x, hitTestPointForThisLayer.y, apzc);
+    HitTestResult hitResult = aNode->HitTest(point);
+    if (hitResult != HitTestResult::NoApzcHit) {
+      APZCTM_LOG("Successfully matched untransformed point %s to visible region for APZC %p via node %p\n",
+        Stringify(hitTestPointForThisLayer.As2DPoint()).c_str(), apzc, aNode);
       result = apzc;
+      MOZ_ASSERT(hitResult == ApzcHitRegion || hitResult == ApzcContentRegion);
       // If event regions are disabled, *aOutHitResult will be ApzcHitRegion
-      *aOutHitResult = (apzc->DispatchToContentRegionContains(point) ? ApzcContentRegion : 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->IsOverscrolled()) {
     *aOutHitResult = OverscrolledApzc;
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -965,49 +965,29 @@ private:
 
 
   /* ===================================================================
    * The functions and members in this section are used to maintain the
    * area that this APZC instance is responsible for. This is used when
    * hit-testing to see which APZC instance should handle touch events.
    */
 public:
-  void SetLayerHitTestData(const EventRegions& aRegions, const Matrix4x4& aTransformToLayer) {
-    mEventRegions = aRegions;
+  void SetAncestorTransform(const Matrix4x4& aTransformToLayer) {
     mAncestorTransform = aTransformToLayer;
   }
 
-  void AddHitTestRegions(const EventRegions& aRegions) {
-    mEventRegions.OrWith(aRegions);
-  }
-
   Matrix4x4 GetAncestorTransform() const {
     return mAncestorTransform;
   }
 
-  bool HitRegionContains(const ParentLayerPoint& aPoint) const {
-    ParentLayerIntPoint point = RoundedToInt(aPoint);
-    return mEventRegions.mHitRegion.Contains(point.x, point.y);
-  }
-
-  bool DispatchToContentRegionContains(const ParentLayerPoint& aPoint) const {
-    ParentLayerIntPoint point = RoundedToInt(aPoint);
-    return mEventRegions.mDispatchToContentHitRegion.Contains(point.x, point.y);
-  }
-
   bool IsOverscrolled() const {
     return mX.IsOverscrolled() || mY.IsOverscrolled();
   }
 
 private:
-  /* This is the union of the hit regions of the layers that this APZC
-   * corresponds to, in the local screen pixels of those layers. (This is the
-   * same coordinate system in which this APZC receives events in
-   * ReceiveInputEvent()). */
-  EventRegions mEventRegions;
   /* This is the cumulative CSS transform for all the layers from (and including)
    * the parent APZC down to (but excluding) this one. */
   Matrix4x4 mAncestorTransform;
 
 
   /* ===================================================================
    * The functions and members in this section are used for sharing the
    * FrameMetrics across processes for the progressive tiling code.
--- a/gfx/layers/apz/src/HitTestingTreeNode.cpp
+++ b/gfx/layers/apz/src/HitTestingTreeNode.cpp
@@ -1,19 +1,22 @@
 /* -*- 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"
+#include "AsyncPanZoomController.h"                     // for AsyncPanZoomController
+#include "LayersLogging.h"                              // for Stringify
+#include "mozilla/gfx/Point.h"                          // for Point4D
+#include "mozilla/layers/AsyncCompositionManager.h"     // for ViewTransform::operator Matrix4x4()
+#include "nsPrintfCString.h"                            // for nsPrintfCString
+#include "UnitTransforms.h"                             // for ViewAs
 
 namespace mozilla {
 namespace layers {
 
 HitTestingTreeNode::HitTestingTreeNode(AsyncPanZoomController* aApzc,
                                        bool aIsPrimaryHolder)
   : mApzc(aApzc)
   , mIsPrimaryApzcHolder(aIsPrimaryHolder)
@@ -111,22 +114,71 @@ HitTestingTreeNode::Apzc() const
 
 bool
 HitTestingTreeNode::IsPrimaryHolder() const
 {
   return mIsPrimaryApzcHolder;
 }
 
 void
+HitTestingTreeNode::SetHitTestData(const EventRegions& aRegions,
+                                   const gfx::Matrix4x4& aTransform,
+                                   const nsIntRegion& aClipRegion)
+{
+  mEventRegions = aRegions;
+  mTransform = aTransform;
+  mClipRegion = aClipRegion;
+}
+
+HitTestResult
+HitTestingTreeNode::HitTest(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
+  if (!mClipRegion.Contains(aPoint.x, aPoint.y)) {
+    return HitTestResult::NoApzcHit;
+  }
+
+  // convert into Layer coordinate space
+  gfx::Matrix4x4 localTransform = mTransform * gfx::Matrix4x4(mApzc->GetCurrentAsyncTransform());
+  gfx::Point4D pointInLayerPixels = localTransform.Inverse().ProjectPoint(aPoint.ToUnknownPoint());
+  if (!pointInLayerPixels.HasPositiveWCoord()) {
+    return HitTestResult::NoApzcHit;
+  }
+  LayerIntPoint point = RoundedToInt(ViewAs<LayerPixel>(pointInLayerPixels.As2DPoint()));
+
+  // 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;
+  }
+  return HitTestResult::ApzcHitRegion;
+}
+
+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());
+  printf_stderr("%sHitTestingTreeNode (%p) APZC (%p) g=(%s) r=(%s) t=(%s) c=(%s)\n",
+    aPrefix, this, mApzc.get(), Stringify(mApzc->GetGuid()).c_str(),
+    Stringify(mEventRegions).c_str(), Stringify(mTransform).c_str(),
+    Stringify(mClipRegion).c_str());
   if (mLastChild) {
     mLastChild->Dump(nsPrintfCString("%s  ", aPrefix).get());
   }
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/apz/src/HitTestingTreeNode.h
+++ b/gfx/layers/apz/src/HitTestingTreeNode.h
@@ -2,18 +2,21 @@
 /* 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"
+#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 "nsRefPtr.h"                       // for nsRefPtr
 
 namespace mozilla {
 namespace layers {
 
 class AsyncPanZoomController;
 
 /**
  * This class represents a node in a tree that is used by the APZCTreeManager
@@ -62,24 +65,56 @@ public:
   HitTestingTreeNode* GetLastChild() const;
   HitTestingTreeNode* GetPrevSibling() const;
   HitTestingTreeNode* GetParent() const;
 
   /* APZC related methods */
   AsyncPanZoomController* Apzc() const;
   bool IsPrimaryHolder() const;
 
+  /* Hit test related methods */
+  void SetHitTestData(const EventRegions& aRegions,
+                      const gfx::Matrix4x4& aTransform,
+                      const nsIntRegion& aClipRegion);
+  HitTestResult HitTest(const ParentLayerPoint& aPoint) const;
+
   /* Debug helpers */
   void Dump(const char* aPrefix = "") const;
 
 private:
   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". */
+  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
+   * 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;
 };
 
 }
 }
 
 #endif // mozilla_layers_HitTestingTreeNode_h
--- a/gfx/tests/gtest/TestAsyncPanZoomController.cpp
+++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp
@@ -1683,25 +1683,25 @@ protected:
     FrameMetrics metrics;
     metrics.SetScrollId(aScrollId);
     nsIntRect layerBound = aLayer->GetVisibleRegion().GetBounds();
     metrics.mCompositionBounds = ParentLayerRect(layerBound.x, layerBound.y,
                                                  layerBound.width, layerBound.height);
     metrics.SetScrollableRect(aScrollableRect);
     metrics.SetScrollOffset(CSSPoint(0, 0));
     aLayer->SetFrameMetrics(metrics);
+    aLayer->SetClipRect(&layerBound);
     if (!aScrollableRect.IsEqualEdges(CSSRect(-1, -1, -1, -1))) {
       // The purpose of this is to roughly mimic what layout would do in the
       // case of a scrollable frame with the event regions and clip. This lets
       // us exercise the hit-testing code in APZCTreeManager
       EventRegions er = aLayer->GetEventRegions();
       nsIntRect scrollRect = LayerIntRect::ToUntyped(RoundedToInt(aScrollableRect * metrics.LayersPixelsPerCSSPixel()));
       er.mHitRegion = nsIntRegion(nsIntRect(layerBound.TopLeft(), scrollRect.Size()));
       aLayer->SetEventRegions(er);
-      aLayer->SetClipRect(&layerBound);
     }
   }
 
   void SetScrollHandoff(Layer* aChild, Layer* aParent) {
     FrameMetrics metrics = aChild->GetFrameMetrics(0);
     metrics.SetScrollParentId(aParent->GetFrameMetrics(0).GetScrollId());
     aChild->SetFrameMetrics(metrics);
   }
@@ -2439,17 +2439,22 @@ TEST_F(APZOverscrollHandoffTester, Scrol
 
 class APZEventRegionsTester : public APZCTreeManagerTester {
 protected:
   UniquePtr<ScopedLayerTreeRegistration> registration;
   TestAsyncPanZoomController* rootApzc;
 
   void CreateEventRegionsLayerTree1() {
     const char* layerTreeSyntax = "c(tt)";
-    root = CreateLayerTree(layerTreeSyntax, nullptr, nullptr, lm, layers);
+    nsIntRegion layerVisibleRegions[] = {
+      nsIntRegion(nsIntRect(0, 0, 200, 200)),     // root
+      nsIntRegion(nsIntRect(0, 0, 100, 200)),     // left half
+      nsIntRegion(nsIntRect(0, 100, 200, 100)),   // bottom half
+    };
+    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers);
     SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID);
     SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1);
     SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 2);
     SetScrollHandoff(layers[1], root);
     SetScrollHandoff(layers[2], root);
 
     // Set up the event regions over a 200x200 area. The root layer has the
     // whole 200x200 as the hit region; layers[1] has the left half and
@@ -2467,17 +2472,21 @@ protected:
 
     registration = MakeUnique<ScopedLayerTreeRegistration>(0, root, mcc);
     manager->UpdatePanZoomControllerTree(nullptr, root, false, 0, 0);
     rootApzc = ApzcOf(root);
   }
 
   void CreateEventRegionsLayerTree2() {
     const char* layerTreeSyntax = "c(t)";
-    root = CreateLayerTree(layerTreeSyntax, nullptr, nullptr, lm, layers);
+    nsIntRegion layerVisibleRegions[] = {
+      nsIntRegion(nsIntRect(0, 0, 100, 500)),
+      nsIntRegion(nsIntRect(0, 150, 100, 100)),
+    };
+    root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers);
     SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID);
 
     // Set up the event regions so that the child thebes layer is positioned far
     // away from the scrolling container layer.
     EventRegions regions(nsIntRegion(nsIntRect(0, 0, 100, 100)));
     root->SetEventRegions(regions);
     regions.mHitRegion = nsIntRegion(nsIntRect(0, 150, 100, 100));
     layers[1]->SetEventRegions(regions);