diff --git a/gfx/layers/composite/APZCTreeManager.cpp b/gfx/layers/composite/APZCTreeManager.cpp index 56ba7d6942c..974c16e35de 100644 --- a/gfx/layers/composite/APZCTreeManager.cpp +++ b/gfx/layers/composite/APZCTreeManager.cpp @@ -141,7 +141,7 @@ APZCTreeManager::UpdatePanZoomControllerTree(CompositorParent* aCompositor, 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, @@ -629,30 +629,42 @@ APZCTreeManager::GetAPZCAtPoint(AsyncPanZoomController* aApzc, const gfxPoint& a // 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; diff --git a/gfx/layers/ipc/AsyncPanZoomController.h b/gfx/layers/ipc/AsyncPanZoomController.h index 4c1906a4c4c..82c6be9b25d 100644 --- a/gfx/layers/ipc/AsyncPanZoomController.h +++ b/gfx/layers/ipc/AsyncPanZoomController.h @@ -655,7 +655,7 @@ private: * 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; @@ -670,16 +670,15 @@ public: 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; diff --git a/gfx/tests/gtest/TestAsyncPanZoomController.cpp b/gfx/tests/gtest/TestAsyncPanZoomController.cpp index e1ec6477760..6283afa6638 100644 --- a/gfx/tests/gtest/TestAsyncPanZoomController.cpp +++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp @@ -433,19 +433,17 @@ TEST(AsyncPanZoomController, OverScrollPanning) { EXPECT_EQ(pointOut, ScreenPoint(0, 90)); } +// Layer tree for HitTesting1 static already_AddRefed -CreateTestLayerTree(nsRefPtr& aLayerManager, nsTArray >& aLayers) { - const char* layerTreeSyntax = "c(ttccc(c(c)))"; - // LayerID 0 12345 6 7 +CreateTestLayerTree1(nsRefPtr& aLayerManager, nsTArray >& 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(), @@ -453,15 +451,35 @@ CreateTestLayerTree(nsRefPtr& aLayerManager, nsTArray +CreateTestLayerTree2(nsRefPtr& aLayerManager, nsTArray >& 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; @@ -469,8 +487,8 @@ SetScrollableFrameMetrics(Layer* aLayer, FrameMetrics::ViewID aScrollId, MockCon 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); } @@ -499,10 +517,11 @@ GetTargetAPZC(APZCTreeManager* manager, const ScreenPoint& aPoint, return hit.forget(); } -TEST(APZCTreeManager, GetAPZCAtPoint) { +// A simple hit testing test that doesn't involve any transforms on layers. +TEST(APZCTreeManager, HitTesting1) { nsTArray > layers; nsRefPtr lm; - nsRefPtr root = CreateTestLayerTree(lm, layers); + nsRefPtr root = CreateTestLayerTree1(lm, layers); TimeStamp testStartTime = TimeStamp::Now(); AsyncPanZoomController::SetFrameTime(testStartTime); @@ -521,8 +540,8 @@ TEST(APZCTreeManager, GetAPZCAtPoint) { 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) @@ -530,8 +549,8 @@ TEST(APZCTreeManager, GetAPZCAtPoint) { 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()); @@ -542,8 +561,8 @@ TEST(APZCTreeManager, GetAPZCAtPoint) { // 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) @@ -567,59 +586,115 @@ TEST(APZCTreeManager, GetAPZCAtPoint) { 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 > layers; + nsRefPtr lm; + nsRefPtr root = CreateTestLayerTree2(lm, layers); + TimeStamp testStartTime = TimeStamp::Now(); + AsyncPanZoomController::SetFrameTime(testStartTime); + nsRefPtr mcc = new MockContentController(); + ScopedLayerTreeRegistration controller(0, root, mcc); + + nsRefPtr manager = new TestAPZCTreeManager(); + nsRefPtr 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