/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 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 "APZCTreeManagerTester.h" #include "APZTestCommon.h" #include "InputUtils.h" #include "mozilla/layers/LayersTypes.h" class APZEventRegionsTester : public APZCTreeManagerTester { protected: UniquePtr registration; TestAsyncPanZoomController* rootApzc; void CreateEventRegionsLayerTree1() { const char* layerTreeSyntax = "c(tt)"; nsIntRegion layerVisibleRegions[] = { nsIntRegion(IntRect(0, 0, 200, 200)), // root nsIntRegion(IntRect(0, 0, 100, 200)), // left half nsIntRegion(IntRect(0, 100, 200, 100)), // bottom half }; root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers); SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID); SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID + 1); SetScrollableFrameMetrics(layers[2], ScrollableLayerGuid::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 // layers[2] has the bottom half. The bottom-left 100x100 area is also // in the d-t-c region for both layers[1] and layers[2] (but layers[2] is // on top so it gets the events by default if the main thread doesn't // respond). EventRegions regions(nsIntRegion(IntRect(0, 0, 200, 200))); root->SetEventRegions(regions); regions.mDispatchToContentHitRegion = nsIntRegion(IntRect(0, 100, 100, 100)); regions.mHitRegion = nsIntRegion(IntRect(0, 0, 100, 200)); layers[1]->SetEventRegions(regions); regions.mHitRegion = nsIntRegion(IntRect(0, 100, 200, 100)); layers[2]->SetEventRegions(regions); registration = MakeUnique(manager, LayersId{0}, root, mcc); UpdateHitTestingTree(); rootApzc = ApzcOf(root); } void CreateEventRegionsLayerTree2() { const char* layerTreeSyntax = "c(t)"; nsIntRegion layerVisibleRegions[] = { nsIntRegion(IntRect(0, 0, 100, 500)), nsIntRegion(IntRect(0, 150, 100, 100)), }; root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers); SetScrollableFrameMetrics(root, ScrollableLayerGuid::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(IntRect(0, 0, 100, 100))); root->SetEventRegions(regions); regions.mHitRegion = nsIntRegion(IntRect(0, 150, 100, 100)); layers[1]->SetEventRegions(regions); registration = MakeUnique(manager, LayersId{0}, root, mcc); UpdateHitTestingTree(); rootApzc = ApzcOf(root); } void CreateObscuringLayerTree() { const char* layerTreeSyntax = "c(c(t)t)"; // LayerID 0 1 2 3 // 0 is the root. // 1 is a parent scrollable layer. // 2 is a child scrollable layer. // 3 is the Obscurer, who ruins everything. nsIntRegion layerVisibleRegions[] = { // x coordinates are uninteresting nsIntRegion(IntRect(0, 0, 200, 200)), // [0, 200] nsIntRegion(IntRect(0, 0, 200, 200)), // [0, 200] nsIntRegion(IntRect(0, 100, 200, 50)), // [100, 150] nsIntRegion(IntRect(0, 100, 200, 100)) // [100, 200] }; root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers); SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, CSSRect(0, 0, 200, 200)); SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID + 1, CSSRect(0, 0, 200, 300)); SetScrollableFrameMetrics(layers[2], ScrollableLayerGuid::START_SCROLL_ID + 2, CSSRect(0, 0, 200, 100)); SetScrollHandoff(layers[2], layers[1]); SetScrollHandoff(layers[1], root); EventRegions regions(nsIntRegion(IntRect(0, 0, 200, 200))); root->SetEventRegions(regions); regions.mHitRegion = nsIntRegion(IntRect(0, 0, 200, 300)); layers[1]->SetEventRegions(regions); regions.mHitRegion = nsIntRegion(IntRect(0, 100, 200, 100)); layers[2]->SetEventRegions(regions); registration = MakeUnique(manager, LayersId{0}, root, mcc); UpdateHitTestingTree(); rootApzc = ApzcOf(root); } void CreateBug1119497LayerTree() { const char* layerTreeSyntax = "c(tt)"; // LayerID 0 12 // 0 is the root and has an APZC // 1 is behind 2 and has an APZC // 2 entirely covers 1 and should take all the input events, but has no APZC // so hits to 2 should go to to the root APZC nsIntRegion layerVisibleRegions[] = { nsIntRegion(IntRect(0, 0, 100, 100)), nsIntRegion(IntRect(0, 0, 100, 100)), nsIntRegion(IntRect(0, 0, 100, 100)), }; root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers); SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID); SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID + 1); registration = MakeUnique(manager, LayersId{0}, root, mcc); UpdateHitTestingTree(); } void CreateBug1117712LayerTree() { const char* layerTreeSyntax = "c(c(t)t)"; // LayerID 0 1 2 3 // 0 is the root // 1 is a container layer whose sole purpose to make a non-empty ancestor // transform for 2, so that 2's screen-to-apzc and apzc-to-gecko // transforms are different from 3's. // 2 is a small layer that is the actual target // 3 is a big layer obscuring 2 with a dispatch-to-content region nsIntRegion layerVisibleRegions[] = { nsIntRegion(IntRect(0, 0, 100, 100)), nsIntRegion(IntRect(0, 0, 0, 0)), nsIntRegion(IntRect(0, 0, 10, 10)), nsIntRegion(IntRect(0, 0, 100, 100)), }; Matrix4x4 layerTransforms[] = { Matrix4x4(), Matrix4x4::Translation(50, 0, 0), Matrix4x4(), Matrix4x4(), }; root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, layerTransforms, lm, layers); SetScrollableFrameMetrics(layers[2], ScrollableLayerGuid::START_SCROLL_ID, CSSRect(0, 0, 10, 10)); SetScrollableFrameMetrics(layers[3], ScrollableLayerGuid::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 100)); SetScrollHandoff(layers[3], layers[2]); EventRegions regions(nsIntRegion(IntRect(0, 0, 10, 10))); layers[2]->SetEventRegions(regions); regions.mHitRegion = nsIntRegion(IntRect(0, 0, 100, 100)); regions.mDispatchToContentHitRegion = nsIntRegion(IntRect(0, 0, 100, 100)); layers[3]->SetEventRegions(regions); registration = MakeUnique(manager, LayersId{0}, root, mcc); UpdateHitTestingTree(); } }; TEST_F(APZEventRegionsTester, HitRegionImmediateResponse) { SCOPED_GFX_VAR(UseWebRender, bool, false); CreateEventRegionsLayerTree1(); TestAsyncPanZoomController* root = ApzcOf(layers[0]); TestAsyncPanZoomController* left = ApzcOf(layers[1]); TestAsyncPanZoomController* bottom = ApzcOf(layers[2]); MockFunction check; { InSequence s; EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, _, left->GetGuid(), _)) .Times(1); EXPECT_CALL(check, Call("Tapped on left")); EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, _, bottom->GetGuid(), _)) .Times(1); EXPECT_CALL(check, Call("Tapped on bottom")); EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, _, root->GetGuid(), _)) .Times(1); EXPECT_CALL(check, Call("Tapped on root")); EXPECT_CALL(check, Call("Tap pending on d-t-c region")); EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, _, bottom->GetGuid(), _)) .Times(1); EXPECT_CALL(check, Call("Tapped on bottom again")); EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, _, left->GetGuid(), _)) .Times(1); EXPECT_CALL(check, Call("Tapped on left this time")); } TimeDuration tapDuration = TimeDuration::FromMilliseconds(100); // Tap in the exposed hit regions of each of the layers once and ensure // the clicks are dispatched right away Tap(manager, ScreenIntPoint(10, 10), tapDuration); mcc->RunThroughDelayedTasks(); // this runs the tap event check.Call("Tapped on left"); Tap(manager, ScreenIntPoint(110, 110), tapDuration); mcc->RunThroughDelayedTasks(); // this runs the tap event check.Call("Tapped on bottom"); Tap(manager, ScreenIntPoint(110, 10), tapDuration); mcc->RunThroughDelayedTasks(); // this runs the tap event check.Call("Tapped on root"); // Now tap on the dispatch-to-content region where the layers overlap Tap(manager, ScreenIntPoint(10, 110), tapDuration); mcc->RunThroughDelayedTasks(); // this runs the main-thread timeout check.Call("Tap pending on d-t-c region"); mcc->RunThroughDelayedTasks(); // this runs the tap event check.Call("Tapped on bottom again"); // Now let's do that again, but simulate a main-thread response uint64_t inputBlockId = 0; Tap(manager, ScreenIntPoint(10, 110), tapDuration, nullptr, &inputBlockId); nsTArray targets; targets.AppendElement(left->GetGuid()); manager->SetTargetAPZC(inputBlockId, targets); while (mcc->RunThroughDelayedTasks()) ; // this runs the tap event check.Call("Tapped on left this time"); } TEST_F(APZEventRegionsTester, HitRegionAccumulatesChildren) { CreateEventRegionsLayerTree2(); // Tap in the area of the child layer that's not directly included in the // parent layer's hit region. Verify that it comes out of the APZC's // content controller, which indicates the input events got routed correctly // to the APZC. EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, _, rootApzc->GetGuid(), _)) .Times(1); Tap(manager, ScreenIntPoint(10, 160), TimeDuration::FromMilliseconds(100)); } TEST_F(APZEventRegionsTester, Obscuration) { SCOPED_GFX_VAR(UseWebRender, bool, false); CreateObscuringLayerTree(); ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); UpdateHitTestingTree(); RefPtr parent = ApzcOf(layers[1]); TestAsyncPanZoomController* child = ApzcOf(layers[2]); Pan(parent, 75, 25, PanOptions::NoFling); APZCTreeManager::HitTestResult hit = manager->GetTargetAPZC(ScreenPoint(50, 75)); EXPECT_EQ(child, hit.mTargetApzc.get()); EXPECT_EQ(hit.mHitResult, CompositorHitTestFlags::eVisibleToHitTest); } TEST_F(APZEventRegionsTester, Bug1119497) { CreateBug1119497LayerTree(); APZCTreeManager::HitTestResult hit = manager->GetTargetAPZC(ScreenPoint(50, 50)); // We should hit layers[2], so |result| will be eVisibleToHitTest but there's // no actual APZC on layers[2], so it will be the APZC of the root layer. EXPECT_EQ(ApzcOf(layers[0]), hit.mTargetApzc.get()); EXPECT_EQ(hit.mHitResult, CompositorHitTestFlags::eVisibleToHitTest); } TEST_F(APZEventRegionsTester, Bug1117712) { CreateBug1117712LayerTree(); TestAsyncPanZoomController* apzc2 = ApzcOf(layers[2]); // These touch events should hit the dispatch-to-content region of layers[3] // and so get queued with that APZC as the tentative target. uint64_t inputBlockId = 0; Tap(manager, ScreenIntPoint(55, 5), TimeDuration::FromMilliseconds(100), nullptr, &inputBlockId); // But now we tell the APZ that really it hit layers[2], and expect the tap // to be delivered at the correct coordinates. EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(55, 5), 0, apzc2->GetGuid(), _)) .Times(1); nsTArray targets; targets.AppendElement(apzc2->GetGuid()); manager->SetTargetAPZC(inputBlockId, targets); } // Test that APZEventResult::mHandledResult is correctly // populated. TEST_F(APZEventRegionsTester, HandledByRootApzcFlag) { // Create simple layer tree containing a dispatch-to-content region // that covers part but not all of its area. const char* layerTreeSyntax = "c"; nsIntRegion layerVisibleRegions[] = { nsIntRegion(IntRect(0, 0, 100, 100)), }; root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers); SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, CSSRect(0, 0, 100, 200)); ModifyFrameMetrics(root, [](ScrollMetadata& sm, FrameMetrics& metrics) { metrics.SetIsRootContent(true); }); // away from the scrolling container layer. EventRegions regions(nsIntRegion(IntRect(0, 0, 100, 100))); // bottom half is dispatch-to-content regions.mDispatchToContentHitRegion = nsIntRegion(IntRect(0, 50, 100, 50)); root->SetEventRegions(regions); registration = MakeUnique(manager, LayersId{0}, root, mcc); UpdateHitTestingTree(); // Tap the top half and check that we report that the event was // handled by the root APZC. APZEventResult result = TouchDown(manager, ScreenIntPoint(50, 25), mcc->Time()); TouchUp(manager, ScreenIntPoint(50, 25), mcc->Time()); EXPECT_EQ(result.mHandledResult, Some(APZHandledResult::HandledByRoot)); // Tap the bottom half and check that we report that we're not // sure whether the event was handled by the root APZC. result = TouchDown(manager, ScreenIntPoint(50, 75), mcc->Time()); TouchUp(manager, ScreenIntPoint(50, 75), mcc->Time()); EXPECT_EQ(result.mHandledResult, Nothing()); // Register an input block callback that will tell us the // delayed answer. APZHandledResult delayedAnswer = APZHandledResult::Invalid; manager->AddInputBlockCallback(result.mInputBlockId, [&](uint64_t id, APZHandledResult answer) { EXPECT_EQ(id, result.mInputBlockId); delayedAnswer = answer; }); // Send APZ the relevant notifications to allow it to process the // input block. manager->SetAllowedTouchBehavior(result.mInputBlockId, {AllowedTouchBehavior::VERTICAL_PAN}); manager->SetTargetAPZC(result.mInputBlockId, {result.mTargetGuid}); manager->ContentReceivedInputBlock(result.mInputBlockId, /*preventDefault=*/false); // Check that we received the delayed answer and it is what we expect. EXPECT_EQ(delayedAnswer, APZHandledResult::HandledByRoot); // Now repeat the tap on the bottom half, but simulate a prevent-default. // This time, we expect a delayed answer of `HandledByContent`. result = TouchDown(manager, ScreenIntPoint(50, 75), mcc->Time()); TouchUp(manager, ScreenIntPoint(50, 75), mcc->Time()); EXPECT_EQ(result.mHandledResult, Nothing()); manager->AddInputBlockCallback(result.mInputBlockId, [&](uint64_t id, APZHandledResult answer) { EXPECT_EQ(id, result.mInputBlockId); delayedAnswer = answer; }); manager->SetAllowedTouchBehavior(result.mInputBlockId, {AllowedTouchBehavior::VERTICAL_PAN}); manager->SetTargetAPZC(result.mInputBlockId, {result.mTargetGuid}); manager->ContentReceivedInputBlock(result.mInputBlockId, /*preventDefault=*/true); EXPECT_EQ(delayedAnswer, APZHandledResult::HandledByContent); // Shrink the scrollable area, now it's no longer scrollable. ModifyFrameMetrics(root, [](ScrollMetadata& sm, FrameMetrics& metrics) { metrics.SetScrollableRect(CSSRect(0, 0, 100, 100)); }); UpdateHitTestingTree(); // Now repeat the tap on the bottom half with an event handler. // This time, we expect a delayed answer of `Unhandled`. result = TouchDown(manager, ScreenIntPoint(50, 75), mcc->Time()); TouchUp(manager, ScreenIntPoint(50, 75), mcc->Time()); EXPECT_EQ(result.mHandledResult, Nothing()); manager->AddInputBlockCallback(result.mInputBlockId, [&](uint64_t id, APZHandledResult answer) { EXPECT_EQ(id, result.mInputBlockId); delayedAnswer = answer; }); manager->SetAllowedTouchBehavior(result.mInputBlockId, {AllowedTouchBehavior::VERTICAL_PAN}); manager->SetTargetAPZC(result.mInputBlockId, {result.mTargetGuid}); manager->ContentReceivedInputBlock(result.mInputBlockId, /*preventDefault=*/false); EXPECT_EQ(delayedAnswer, APZHandledResult::Unhandled); }