Bug 932525 - Do APZC hit testing using the layer's screen coordinates rather than layer coordinates. r=kats
authorBotond Ballo <botond@mozilla.com>
Wed, 30 Oct 2013 15:58:25 -0400
changeset 153505 4361037d9a4ccff60170bfa070c2154b91ed19d3
parent 153423 61a9fdab3f5ae0cd4c037ded3e0f32e7ce591cbd
child 153506 233e43e38854bce11a097091c94ef15e1fe60cbc
push id35816
push userkwierso@gmail.com
push dateTue, 05 Nov 2013 05:22:53 +0000
treeherdermozilla-inbound@442b47e9fb80 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats
bugs932525
milestone28.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 932525 - Do APZC hit testing using the layer's screen coordinates rather than layer coordinates. r=kats
gfx/layers/composite/APZCTreeManager.cpp
gfx/layers/ipc/AsyncPanZoomController.h
gfx/tests/gtest/TestAsyncPanZoomController.cpp
--- a/gfx/layers/composite/APZCTreeManager.cpp
+++ b/gfx/layers/composite/APZCTreeManager.cpp
@@ -136,17 +136,17 @@ APZCTreeManager::UpdatePanZoomController
           apzc->SetPrevSibling(nullptr);
           apzc->SetLastChild(nullptr);
         }
         APZC_LOG("Using APZC %p for layer %p with identifiers %lld %lld\n", apzc, aLayer, aLayersId, container->GetFrameMetrics().mScrollId);
 
         apzc->NotifyLayersUpdated(container->GetFrameMetrics(),
                                         aIsFirstPaint && (aLayersId == aFirstPaintLayersId));
 
-        LayerRect visible = ScreenRect(container->GetFrameMetrics().mCompositionBounds) * ScreenToLayerScale(1.0);
+        ScreenRect visible(container->GetFrameMetrics().mCompositionBounds);
         apzc->SetLayerHitTestData(visible, aTransform, aLayer->GetTransform());
         APZC_LOG("Setting rect(%f %f %f %f) as visible region for APZC %p\n", visible.x, visible.y,
                                                                               visible.width, visible.height,
                                                                               apzc);
 
         // Bind the APZC instance into the tree of APZCs
         if (aNextSibling) {
           aNextSibling->SetPrevSibling(apzc);
@@ -624,40 +624,52 @@ APZCTreeManager::FindTargetAPZC(AsyncPan
 AsyncPanZoomController*
 APZCTreeManager::GetAPZCAtPoint(AsyncPanZoomController* aApzc, const gfxPoint& aHitTestPoint)
 {
   // The comments below assume there is a chain of layers L..R with L and P having APZC instances as
   // explained in the comment on GetInputTransforms. This function will recurse with aApzc 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 is OC.Inverse() * NC.Inverse() * MC.Inverse() at recursion level for L,
-  //                    and RC.Inverse() * QC.Inverse()                at recursion level for P.
+  // ancestorUntransform takes points from aApzc's parent APZC's screen coordinates
+  // to aApzc's screen coordinates.
+  // It is OC.Inverse() * NC.Inverse() * MC.Inverse() at recursion level for L,
+  //   and RC.Inverse() * QC.Inverse()                at recursion level for P.
   gfx3DMatrix ancestorUntransform = aApzc->GetAncestorTransform().Inverse();
-  // asyncUntransform is LA.Inverse() at recursion level for L,
-  //                 and PA.Inverse() at recursion level for P.
-  gfx3DMatrix asyncUntransform = gfx3DMatrix(aApzc->GetCurrentAsyncTransform()).Inverse();
-  // untransformSinceLastApzc is OC.Inverse() * NC.Inverse() * MC.Inverse() * LA.Inverse() * LC.Inverse() at L,
-  //                         and RC.Inverse() * QC.Inverse() * PA.Inverse() * PC.Inverse()                at P.
-  gfx3DMatrix untransformSinceLastApzc = ancestorUntransform * asyncUntransform * aApzc->GetCSSTransform().Inverse();
-  // untransformed is the user input in L's layer space at L,
-  //                             and in P's layer space at P.
-  gfxPoint untransformed = untransformSinceLastApzc.ProjectPoint(aHitTestPoint);
-  APZC_LOG("Untransformed %f %f to %f %f for APZC %p\n", aHitTestPoint.x, aHitTestPoint.y, untransformed.x, untransformed.y, aApzc);
+
+  // Hit testing for this layer is performed in aApzc's screen coordinates.
+  gfxPoint hitTestPointForThisLayer = ancestorUntransform.ProjectPoint(aHitTestPoint);
+  APZC_LOG("Untransformed %f %f to screen coordinates %f %f for hit-testing APZC %p\n",
+           aHitTestPoint.x, aHitTestPoint.y,
+           hitTestPointForThisLayer.x, hitTestPointForThisLayer.y, aApzc);
+
+  // myUntransform takes points from aApzc's screen coordinates
+  // to aApzc's layer coordinates (which are aApzc's children's screen coordinates).
+  // It is LA.Inverse() * LC.Inverse() at L
+  //   and PA.Inverse() * PC.Inverse() at P.
+  gfx3DMatrix myUntransform = gfx3DMatrix(aApzc->GetCurrentAsyncTransform()).Inverse()
+                            * aApzc->GetCSSTransform().Inverse();
+
+  // Hit testing for child layers is performed in aApzc's layer coordinates.
+  gfxPoint hitTestPointForChildLayers = myUntransform.ProjectPoint(hitTestPointForThisLayer);
+  APZC_LOG("Untransformed %f %f to layer coordinates %f %f for APZC %p\n",
+           aHitTestPoint.x, aHitTestPoint.y,
+           hitTestPointForChildLayers.x, hitTestPointForChildLayers.y, aApzc);
 
   // This walks the tree in depth-first, reverse order, so that it encounters
   // APZCs front-to-back on the screen.
   for (AsyncPanZoomController* child = aApzc->GetLastChild(); child; child = child->GetPrevSibling()) {
-    AsyncPanZoomController* match = GetAPZCAtPoint(child, untransformed);
+    AsyncPanZoomController* match = GetAPZCAtPoint(child, hitTestPointForChildLayers);
     if (match) {
       return match;
     }
   }
-  if (aApzc->VisibleRegionContains(LayerPoint(untransformed.x, untransformed.y))) {
-    APZC_LOG("Successfully matched untransformed point %f %f to visible region for APZC %p\n", untransformed.x, untransformed.y, aApzc);
+  if (aApzc->VisibleRegionContains(ScreenPoint(hitTestPointForThisLayer.x, hitTestPointForThisLayer.y))) {
+    APZC_LOG("Successfully matched untransformed point %f %f to visible region for APZC %p\n",
+             hitTestPointForThisLayer.x, hitTestPointForThisLayer.y, aApzc);
     return aApzc;
   }
   return nullptr;
 }
 
 /* This function sets the aTransformToApzcOut and aTransformToScreenOut out-parameters
    to some useful transformations that input events may need applied. This is best
    illustrated with an example. Consider a chain of layers, L, M, N, O, P, Q, R. Layer L
--- a/gfx/layers/ipc/AsyncPanZoomController.h
+++ b/gfx/layers/ipc/AsyncPanZoomController.h
@@ -650,41 +650,40 @@ private:
   nsRefPtr<AsyncPanZoomController> mPrevSibling;
   nsRefPtr<AsyncPanZoomController> mParent;
 
   /* 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 LayerRect& aRect, const gfx3DMatrix& aTransformToLayer,
+  void SetLayerHitTestData(const ScreenRect& aRect, const gfx3DMatrix& aTransformToLayer,
                            const gfx3DMatrix& aTransformForLayer) {
     mVisibleRect = aRect;
     mAncestorTransform = aTransformToLayer;
     mCSSTransform = aTransformForLayer;
   }
 
   gfx3DMatrix GetAncestorTransform() const {
     return mAncestorTransform;
   }
 
   gfx3DMatrix GetCSSTransform() const {
     return mCSSTransform;
   }
 
-  bool VisibleRegionContains(const LayerPoint& aPoint) const {
+  bool VisibleRegionContains(const ScreenPoint& aPoint) const {
     return mVisibleRect.Contains(aPoint);
   }
 
 private:
-  /* This is the viewport of the layer that this APZC corresponds to, in
-   * layer pixels. It position here does not account for any transformations
-   * applied to any layers, whether they are CSS transforms or async
-   * transforms. */
-  LayerRect mVisibleRect;
+  /* This is the visible region of the layer that this APZC corresponds to, in
+   * that layer's screen pixels (the same coordinate system in which this APZC
+   * receives events in ReceiveInputEvent()). */
+  ScreenRect mVisibleRect;
   /* This is the cumulative CSS transform for all the layers between the parent
    * APZC and this one (not inclusive) */
   gfx3DMatrix mAncestorTransform;
   /* This is the CSS transform for this APZC's layer. */
   gfx3DMatrix mCSSTransform;
 };
 
 }
--- a/gfx/tests/gtest/TestAsyncPanZoomController.cpp
+++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp
@@ -428,54 +428,72 @@ TEST(AsyncPanZoomController, OverScrollP
   ViewTransform viewTransformOut;
 
   // Pan down
   ApzcPan(apzc, time, touchStart, touchEnd);
   apzc->SampleContentTransformForFrame(testStartTime+TimeDuration::FromMilliseconds(1000), &viewTransformOut, pointOut);
   EXPECT_EQ(pointOut, ScreenPoint(0, 90));
 }
 
+// Layer tree for HitTesting1
 static already_AddRefed<mozilla::layers::Layer>
-CreateTestLayerTree(nsRefPtr<LayerManager>& aLayerManager, nsTArray<nsRefPtr<Layer> >& aLayers) {
-  const char* layerTreeSyntax = "c(ttccc(c(c)))";
-  // LayerID                     0 12345 6 7
+CreateTestLayerTree1(nsRefPtr<LayerManager>& aLayerManager, nsTArray<nsRefPtr<Layer> >& aLayers) {
+  const char* layerTreeSyntax = "c(ttcc)";
+  // LayerID                     0 1234
   nsIntRegion layerVisibleRegion[] = {
     nsIntRegion(nsIntRect(0,0,100,100)),
     nsIntRegion(nsIntRect(0,0,100,100)),
     nsIntRegion(nsIntRect(10,10,20,20)),
     nsIntRegion(nsIntRect(10,10,20,20)),
     nsIntRegion(nsIntRect(5,5,20,20)),
-    nsIntRegion(nsIntRect(10,10,40,40)),
-    nsIntRegion(nsIntRect(10,10,40,40)),
-    nsIntRegion(nsIntRect(10,10,40,40)),
   };
   gfx3DMatrix transforms[] = {
     gfx3DMatrix(),
     gfx3DMatrix(),
     gfx3DMatrix(),
     gfx3DMatrix(),
     gfx3DMatrix(),
-    gfx3DMatrix(),
-    gfx3DMatrix(),
-    gfx3DMatrix(),
   };
   return CreateLayerTree(layerTreeSyntax, layerVisibleRegion, transforms, aLayerManager, aLayers);
 }
 
+// Layer Tree for HitTesting2
+static already_AddRefed<mozilla::layers::Layer>
+CreateTestLayerTree2(nsRefPtr<LayerManager>& aLayerManager, nsTArray<nsRefPtr<Layer> >& aLayers) {
+  const char* layerTreeSyntax = "c(cc(c))";
+  // LayerID                     0 12 3
+  nsIntRegion layerVisibleRegion[] = {
+    nsIntRegion(nsIntRect(0,0,100,100)),
+    nsIntRegion(nsIntRect(10,10,40,40)),
+    nsIntRegion(nsIntRect(10,60,40,40)),
+    nsIntRegion(nsIntRect(10,60,40,40)),
+  };
+  gfx3DMatrix transforms[] = {
+    gfx3DMatrix(),
+    gfx3DMatrix(),
+    gfx3DMatrix(),
+    gfx3DMatrix(),
+  };
+  return CreateLayerTree(layerTreeSyntax, layerVisibleRegion, transforms, aLayerManager, aLayers);
+}
+
 static void
-SetScrollableFrameMetrics(Layer* aLayer, FrameMetrics::ViewID aScrollId, MockContentController* mcc)
+SetScrollableFrameMetrics(Layer* aLayer, FrameMetrics::ViewID aScrollId,
+                          // The scrollable rect is only used in HitTesting2,
+                          // HitTesting1 doesn't care about it.
+                          CSSRect aScrollableRect = CSSRect(-1, -1, -1, -1))
 {
   ContainerLayer* container = aLayer->AsContainerLayer();
   FrameMetrics metrics;
   metrics.mScrollId = aScrollId;
   nsIntRect layerBound = aLayer->GetVisibleRegion().GetBounds();
   metrics.mCompositionBounds = ScreenIntRect(layerBound.x, layerBound.y,
                                              layerBound.width, layerBound.height);
-  metrics.mViewport = CSSRect(layerBound.x, layerBound.y,
-                              layerBound.width, layerBound.height);
+  metrics.mScrollableRect = aScrollableRect;
+  metrics.mScrollOffset = CSSPoint(0, 0);
   container->SetFrameMetrics(metrics);
 }
 
 static gfxPoint
 NudgeToIntegers(const gfxPoint& aPoint)
 {
   // gfxPoint has doubles but NudgeToInteger takes
   // floats so use local vars. The loss in precision
@@ -494,20 +512,21 @@ GetTargetAPZC(APZCTreeManager* manager, 
 {
   nsRefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(aPoint);
   if (hit) {
     manager->GetInputTransforms(hit.get(), aTransformToApzcOut, aTransformToScreenOut);
   }
   return hit.forget();
 }
 
-TEST(APZCTreeManager, GetAPZCAtPoint) {
+// A simple hit testing test that doesn't involve any transforms on layers.
+TEST(APZCTreeManager, HitTesting1) {
   nsTArray<nsRefPtr<Layer> > layers;
   nsRefPtr<LayerManager> lm;
-  nsRefPtr<Layer> root = CreateTestLayerTree(lm, layers);
+  nsRefPtr<Layer> root = CreateTestLayerTree1(lm, layers);
 
   TimeStamp testStartTime = TimeStamp::Now();
   AsyncPanZoomController::SetFrameTime(testStartTime);
   nsRefPtr<MockContentController> mcc = new MockContentController();
   ScopedLayerTreeRegistration controller(0, root, mcc);
 
   nsRefPtr<APZCTreeManager> manager = new TestAPZCTreeManager();
   gfx3DMatrix transformToApzc;
@@ -516,39 +535,39 @@ TEST(APZCTreeManager, GetAPZCAtPoint) {
   // No APZC attached so hit testing will return no APZC at (20,20)
   nsRefPtr<AsyncPanZoomController> hit = GetTargetAPZC(manager, ScreenPoint(20, 20), transformToApzc, transformToScreen);
   AsyncPanZoomController* nullAPZC = nullptr;
   EXPECT_EQ(nullAPZC, hit.get());
   EXPECT_EQ(gfx3DMatrix(), transformToApzc);
   EXPECT_EQ(gfx3DMatrix(), transformToScreen);
 
   // Now we have a root APZC that will match the page
-  SetScrollableFrameMetrics(root, FrameMetrics::ROOT_SCROLL_ID, mcc);
-  manager->UpdatePanZoomControllerTree(nullptr, root, 0, false);
+  SetScrollableFrameMetrics(root, FrameMetrics::ROOT_SCROLL_ID);
+  manager->UpdatePanZoomControllerTree(nullptr, root, false, 0);
   hit = GetTargetAPZC(manager, ScreenPoint(15, 15), transformToApzc, transformToScreen);
   EXPECT_EQ(root->AsContainerLayer()->GetAsyncPanZoomController(), hit.get());
   // expect hit point at LayerIntPoint(15, 15)
   EXPECT_EQ(gfxPoint(15, 15), transformToApzc.Transform(gfxPoint(15, 15)));
   EXPECT_EQ(gfxPoint(15, 15), transformToScreen.Transform(gfxPoint(15, 15)));
 
   // Now we have a sub APZC with a better fit
-  SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID, mcc);
-  manager->UpdatePanZoomControllerTree(nullptr, root, 0, false);
+  SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID);
+  manager->UpdatePanZoomControllerTree(nullptr, root, false, 0);
   EXPECT_NE(root->AsContainerLayer()->GetAsyncPanZoomController(), layers[3]->AsContainerLayer()->GetAsyncPanZoomController());
   hit = GetTargetAPZC(manager, ScreenPoint(15, 15), transformToApzc, transformToScreen);
   EXPECT_EQ(layers[3]->AsContainerLayer()->GetAsyncPanZoomController(), hit.get());
   // expect hit point at LayerIntPoint(15, 15)
   EXPECT_EQ(gfxPoint(15, 15), transformToApzc.Transform(gfxPoint(15, 15)));
   EXPECT_EQ(gfxPoint(15, 15), transformToScreen.Transform(gfxPoint(15, 15)));
 
   // Now test hit testing when we have two scrollable layers
   hit = GetTargetAPZC(manager, ScreenPoint(15, 15), transformToApzc, transformToScreen);
   EXPECT_EQ(layers[3]->AsContainerLayer()->GetAsyncPanZoomController(), hit.get());
-  SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 1, mcc);
-  manager->UpdatePanZoomControllerTree(nullptr, root, 0, false);
+  SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 1);
+  manager->UpdatePanZoomControllerTree(nullptr, root, false, 0);
   hit = GetTargetAPZC(manager, ScreenPoint(15, 15), transformToApzc, transformToScreen);
   EXPECT_EQ(layers[4]->AsContainerLayer()->GetAsyncPanZoomController(), hit.get());
   // expect hit point at LayerIntPoint(15, 15)
   EXPECT_EQ(gfxPoint(15, 15), transformToApzc.Transform(gfxPoint(15, 15)));
   EXPECT_EQ(gfxPoint(15, 15), transformToScreen.Transform(gfxPoint(15, 15)));
 
   // Hit test ouside the reach of layer[3,4] but inside root
   hit = GetTargetAPZC(manager, ScreenPoint(90, 90), transformToApzc, transformToScreen);
@@ -562,64 +581,120 @@ TEST(APZCTreeManager, GetAPZCAtPoint) {
   EXPECT_EQ(nullAPZC, hit.get());
   EXPECT_EQ(gfx3DMatrix(), transformToApzc);
   EXPECT_EQ(gfx3DMatrix(), transformToScreen);
   hit = GetTargetAPZC(manager, ScreenPoint(-1000, 10), transformToApzc, transformToScreen);
   EXPECT_EQ(nullAPZC, hit.get());
   EXPECT_EQ(gfx3DMatrix(), transformToApzc);
   EXPECT_EQ(gfx3DMatrix(), transformToScreen);
 
-  // Test layer transform
-  gfx3DMatrix transform;
-  transform.ScalePost(0.1, 0.1, 1);
-  root->SetBaseTransform(transform);
-  manager->UpdatePanZoomControllerTree(nullptr, root, 0, false);
-  hit = GetTargetAPZC(manager, ScreenPoint(50, 50), transformToApzc, transformToScreen); // This point is now outside the root layer
-  EXPECT_EQ(nullAPZC, hit.get());
-  EXPECT_EQ(gfx3DMatrix(), transformToApzc);
-  EXPECT_EQ(gfx3DMatrix(), transformToScreen);
-
-  // This hit test will hit both layers[3] and layers[4]; layers[4] is later in the tree so
-  // it is a better match
-  hit = GetTargetAPZC(manager, ScreenPoint(2, 2), transformToApzc, transformToScreen);
-  EXPECT_EQ(layers[4]->AsContainerLayer()->GetAsyncPanZoomController(), hit.get());
-  // expect hit point at LayerPoint(20, 20)
-  EXPECT_EQ(gfxPoint(20, 20), NudgeToIntegers(transformToApzc.Transform(gfxPoint(2, 2))));
-  EXPECT_EQ(gfxPoint(2, 2), NudgeToIntegers(transformToScreen.Transform(gfxPoint(20, 20))));
-
-  // Scale layer[4] outside the range
-  layers[4]->SetBaseTransform(transform);
-  // layer 4 effective visible screenrect: (0.05, 0.05, 0.2, 0.2)
-  // Does not contain (2, 2)
-  manager->UpdatePanZoomControllerTree(nullptr, root, 0, false);
-  hit = GetTargetAPZC(manager, ScreenPoint(2, 2), transformToApzc, transformToScreen);
-  EXPECT_EQ(layers[3]->AsContainerLayer()->GetAsyncPanZoomController(), hit.get());
-  // expect hit point at LayerPoint(20, 20)
-  EXPECT_EQ(gfxPoint(20, 20), NudgeToIntegers(transformToApzc.Transform(gfxPoint(2, 2))));
-  EXPECT_EQ(gfxPoint(2, 2), NudgeToIntegers(transformToScreen.Transform(gfxPoint(20, 20))));
-
-  // Transformation chain to layer 7
-  SetScrollableFrameMetrics(layers[7], FrameMetrics::START_SCROLL_ID + 2, mcc);
-
-  gfx3DMatrix translateTransform;
-  translateTransform.Translate(gfxPoint3D(10, 10, 0));
-  layers[5]->SetBaseTransform(translateTransform);
-
-  gfx3DMatrix translateTransform2;
-  translateTransform2.Translate(gfxPoint3D(-20, 0, 0));
-  layers[6]->SetBaseTransform(translateTransform2);
-
-  gfx3DMatrix translateTransform3;
-  translateTransform3.ScalePost(1,15,1);
-  layers[7]->SetBaseTransform(translateTransform3);
-
-  manager->UpdatePanZoomControllerTree(nullptr, root, 0, false);
-  // layer 7 effective visible screenrect (0,16,4,60) but clipped by parent layers
-  hit = GetTargetAPZC(manager, ScreenPoint(1, 45), transformToApzc, transformToScreen);
-  EXPECT_EQ(layers[7]->AsContainerLayer()->GetAsyncPanZoomController(), hit.get());
-  // expect hit point at LayerPoint(20, 440), which is CSSPoint(20, 29)
-  EXPECT_EQ(gfxPoint(20, 440), NudgeToIntegers(transformToApzc.Transform(gfxPoint(1, 45))));
-  EXPECT_EQ(gfxPoint(1, 45), NudgeToIntegers(transformToScreen.Transform(gfxPoint(20, 440))));
-
   manager->ClearTree();
 }
 
+// A more involved hit testing test that involves css and async transforms.
+TEST(APZCTreeManager, HitTesting2) {
+  nsTArray<nsRefPtr<Layer> > layers;
+  nsRefPtr<LayerManager> lm;
+  nsRefPtr<Layer> root = CreateTestLayerTree2(lm, layers);
 
+  TimeStamp testStartTime = TimeStamp::Now();
+  AsyncPanZoomController::SetFrameTime(testStartTime);
+  nsRefPtr<MockContentController> mcc = new MockContentController();
+  ScopedLayerTreeRegistration controller(0, root, mcc);
+
+  nsRefPtr<APZCTreeManager> manager = new TestAPZCTreeManager();
+  nsRefPtr<AsyncPanZoomController> hit;
+  gfx3DMatrix transformToApzc;
+  gfx3DMatrix transformToScreen;
+
+  // Set a CSS transform on one of the layers.
+  gfx3DMatrix transform;
+  transform.ScalePost(2, 1, 1);
+  layers[2]->SetBaseTransform(transform);
+
+  // Make some other layers scrollable.
+  SetScrollableFrameMetrics(root, FrameMetrics::ROOT_SCROLL_ID, CSSRect(0, 0, 200, 200));
+  SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 80, 80));
+  SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 80, 80));
+
+  manager->UpdatePanZoomControllerTree(nullptr, root, false, 0);
+
+  // At this point, the following holds (all coordinates in screen pixels):
+  // layers[0] has content from (0,0)-(200,200), clipped by composition bounds (0,0)-(100,100)
+  // layers[1] has content from (10,10)-(90,90), clipped by composition bounds (10,10)-(50,50)
+  // layers[2] has content from (20,60)-(100,100). no clipping as it's not a scrollable layer
+  // layers[3] has content from (20,60)-(180,140), clipped by composition bounds (20,60)-(100,100)
+
+  AsyncPanZoomController* apzcroot = root->AsContainerLayer()->GetAsyncPanZoomController();
+  AsyncPanZoomController* apzc1 = layers[1]->AsContainerLayer()->GetAsyncPanZoomController();
+  AsyncPanZoomController* apzc3 = layers[3]->AsContainerLayer()->GetAsyncPanZoomController();
+
+  // Hit an area that's clearly on the root layer but not any of the child layers.
+  hit = GetTargetAPZC(manager, ScreenPoint(75, 25), transformToApzc, transformToScreen);
+  EXPECT_EQ(apzcroot, hit.get());
+  EXPECT_EQ(gfxPoint(75, 25), transformToApzc.Transform(gfxPoint(75, 25)));
+  EXPECT_EQ(gfxPoint(75, 25), transformToScreen.Transform(gfxPoint(75, 25)));
+
+  // Hit an area on the root that would be on layers[3] if layers[2]
+  // weren't transformed.
+  // Note that if layers[2] were scrollable, then this would hit layers[2]
+  // because its composition bounds would be at (10,60)-(50,100) (and the
+  // scale-only transform that we set on layers[2] would be invalid because
+  // it would place the layer into overscroll, as its composition bounds
+  // start at x=10 but its content at x=20).
+  hit = GetTargetAPZC(manager, ScreenPoint(15, 75), transformToApzc, transformToScreen);
+  EXPECT_EQ(apzcroot, hit.get());
+  EXPECT_EQ(gfxPoint(15, 75), transformToApzc.Transform(gfxPoint(15, 75)));
+  EXPECT_EQ(gfxPoint(15, 75), transformToScreen.Transform(gfxPoint(15, 75)));
+
+  // Hit an area on layers[1].
+  hit = GetTargetAPZC(manager, ScreenPoint(25, 25), transformToApzc, transformToScreen);
+  EXPECT_EQ(apzc1, hit.get());
+  EXPECT_EQ(gfxPoint(25, 25), transformToApzc.Transform(gfxPoint(25, 25)));
+  EXPECT_EQ(gfxPoint(25, 25), transformToScreen.Transform(gfxPoint(25, 25)));
+
+  // Hit an area on layers[3].
+  hit = GetTargetAPZC(manager, ScreenPoint(25, 75), transformToApzc, transformToScreen);
+  EXPECT_EQ(apzc3, hit.get());
+  // transformToApzc should unapply layers[2]'s transform
+  EXPECT_EQ(gfxPoint(12.5, 75), transformToApzc.Transform(gfxPoint(25, 75)));
+  // and transformToScreen should reapply it
+  EXPECT_EQ(gfxPoint(25, 75), transformToScreen.Transform(gfxPoint(12.5, 75)));
+
+  // Hit an area on layers[3] that would be on the root if layers[2]
+  // weren't transformed.
+  hit = GetTargetAPZC(manager, ScreenPoint(75, 75), transformToApzc, transformToScreen);
+  EXPECT_EQ(apzc3, hit.get());
+  // transformToApzc should unapply layers[2]'s transform
+  EXPECT_EQ(gfxPoint(37.5, 75), transformToApzc.Transform(gfxPoint(75, 75)));
+  // and transformToScreen should reapply it
+  EXPECT_EQ(gfxPoint(75, 75), transformToScreen.Transform(gfxPoint(37.5, 75)));
+
+  // Pan the root layer upward by 50 pixels.
+  // This causes layers[1] to scroll out of view, and an async transform
+  // of -50 to be set on the root layer.
+  int time = 0;
+  // Silence GMock warnings about "uninteresting mock function calls".
+  EXPECT_CALL(*mcc, PostDelayedTask(_,_)).Times(1);
+  EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(2);
+  EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
+  ApzcPan(apzcroot, time, 100, 50);
+
+  // Hit where layers[3] used to be. It should now hit the root.
+  hit = GetTargetAPZC(manager, ScreenPoint(75, 75), transformToApzc, transformToScreen);
+  EXPECT_EQ(apzcroot, hit.get());
+  // transformToApzc doesn't unapply the root's own async transform
+  EXPECT_EQ(gfxPoint(75, 75), transformToApzc.Transform(gfxPoint(75, 75)));
+  // but transformToScreen does
+  EXPECT_EQ(gfxPoint(75, 125), transformToScreen.Transform(gfxPoint(75, 75)));
+
+  // Hit where layers[1] used to be and where layers[3] should now be.
+  hit = GetTargetAPZC(manager, ScreenPoint(25, 25), transformToApzc, transformToScreen);
+  EXPECT_EQ(apzc3, hit.get());
+  // transformToApzc unapplies both layers[2]'s css transform and the root's
+  // async trasnform
+  EXPECT_EQ(gfxPoint(12.5, 75), transformToApzc.Transform(gfxPoint(25, 25)));
+  // transformToScreen reapplies the css transform only (since Gecko doesn't
+  // know about async transforms)
+  EXPECT_EQ(gfxPoint(25, 75), transformToScreen.Transform(gfxPoint(12.5, 75)));
+
+  manager->ClearTree();
+}
\ No newline at end of file