Bug 1109873 - Move hit-testing data into HitTestingTreeNode. r=botond
authorKartikaya Gupta <kgupta@mozilla.com>
Thu, 08 Jan 2015 09:40:01 -0500
changeset 222772 aa2fa9528e520bb8199d1cf11b5911397f1c489b
parent 222771 83aa477d175baf0f4628f4151ce0c827ef7d5fd7
child 222773 7d4c6b7bf0d547b6a5bde43617dbc6bd5cc296ec
push id28073
push userkwierso@gmail.com
push dateFri, 09 Jan 2015 01:08:23 +0000
treeherdermozilla-central@b3f84cf78dc2 [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 - 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);