TestEventResult.cpp (20616B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "APZCTreeManagerTester.h" 8 #include "APZTestCommon.h" 9 #include "InputUtils.h" 10 #include "mozilla/EventForwards.h" 11 #include "mozilla/layers/LayersTypes.h" 12 #include <tuple> 13 14 using LayersUpdateFlags = AsyncPanZoomController::LayersUpdateFlags; 15 16 class APZEventResultTester : public APZCTreeManagerTester { 17 protected: 18 UniquePtr<ScopedLayerTreeRegistration> registration; 19 20 void UpdateOverscrollBehavior(OverscrollBehavior aX, OverscrollBehavior aY) { 21 ModifyFrameMetrics(root, [aX, aY](ScrollMetadata& sm, FrameMetrics& _) { 22 OverscrollBehaviorInfo overscroll; 23 overscroll.mBehaviorX = aX; 24 overscroll.mBehaviorY = aY; 25 sm.SetOverscrollBehavior(overscroll); 26 }); 27 UpdateHitTestingTree(); 28 } 29 30 void SetScrollOffsetOnMainThread(const CSSPoint& aPoint) { 31 RefPtr<TestAsyncPanZoomController> apzc = ApzcOf(root); 32 33 ScrollMetadata metadata = apzc->GetScrollMetadata(); 34 metadata.GetMetrics().SetLayoutScrollOffset(aPoint); 35 nsTArray<ScrollPositionUpdate> scrollUpdates; 36 scrollUpdates.AppendElement(ScrollPositionUpdate::NewScroll( 37 ScrollOrigin::Other, CSSPoint::ToAppUnits(aPoint))); 38 metadata.SetScrollUpdates(scrollUpdates); 39 metadata.GetMetrics().SetScrollGeneration( 40 scrollUpdates.LastElement().GetGeneration()); 41 apzc->NotifyLayersUpdated(metadata, 42 LayersUpdateFlags{.mIsFirstPaint = false, 43 .mThisLayerTreeUpdated = true}); 44 } 45 46 void CreateScrollableRootLayer() { 47 const char* treeShape = "x"; 48 LayerIntRect layerVisibleRects[] = { 49 LayerIntRect(0, 0, 100, 100), 50 }; 51 CreateScrollData(treeShape, layerVisibleRects); 52 SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, 53 CSSRect(0, 0, 200, 200)); 54 ModifyFrameMetrics(root, [](ScrollMetadata& sm, FrameMetrics& metrics) { 55 metrics.SetIsRootContent(true); 56 }); 57 registration = MakeUnique<ScopedLayerTreeRegistration>(LayersId{0}, mcc); 58 UpdateHitTestingTree(); 59 } 60 61 enum class PreventDefaultFlag { No, Yes }; 62 std::tuple<APZEventResult, APZHandledResult> TapDispatchToContent( 63 const ScreenIntPoint& aPoint, PreventDefaultFlag aPreventDefaultFlag) { 64 APZEventResult result = 65 Tap(manager, aPoint, TimeDuration::FromMilliseconds(100)); 66 67 APZHandledResult delayedAnswer{APZHandledPlace::Invalid, SideBits::eNone, 68 ScrollDirections()}; 69 manager->AddInputBlockCallback( 70 result.mInputBlockId, [&](uint64_t id, const APZHandledResult& answer) { 71 EXPECT_EQ(id, result.mInputBlockId); 72 delayedAnswer = answer; 73 }); 74 manager->SetAllowedTouchBehavior(result.mInputBlockId, 75 {AllowedTouchBehavior::VERTICAL_PAN}); 76 manager->SetTargetAPZC(result.mInputBlockId, {result.mTargetGuid}); 77 manager->ContentReceivedInputBlock( 78 result.mInputBlockId, aPreventDefaultFlag == PreventDefaultFlag::Yes); 79 return {result, delayedAnswer}; 80 } 81 82 void OverscrollDirectionsWithEventHandlerTest( 83 PreventDefaultFlag aPreventDefaultFlag) { 84 UpdateHitTestingTree(); 85 86 APZHandledPlace expectedPlace = 87 aPreventDefaultFlag == PreventDefaultFlag::No 88 ? APZHandledPlace::HandledByRoot 89 : APZHandledPlace::HandledByContent; 90 { 91 QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID, 92 {CompositorHitTestFlags::eVisibleToHitTest, 93 CompositorHitTestFlags::eIrregularArea}); 94 auto [result, delayedHandledResult] = 95 TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag); 96 EXPECT_EQ(result.GetHandledResult(), Nothing()); 97 EXPECT_EQ( 98 delayedHandledResult, 99 (APZHandledResult{expectedPlace, SideBits::eBottom | SideBits::eRight, 100 EitherScrollDirection})); 101 } 102 103 // overscroll-behavior: contain, contain. 104 UpdateOverscrollBehavior(OverscrollBehavior::Contain, 105 OverscrollBehavior::Contain); 106 { 107 QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID, 108 {CompositorHitTestFlags::eVisibleToHitTest, 109 CompositorHitTestFlags::eIrregularArea}); 110 auto [result, delayedHandledResult] = 111 TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag); 112 EXPECT_EQ(result.GetHandledResult(), Nothing()); 113 EXPECT_EQ( 114 delayedHandledResult, 115 (APZHandledResult{expectedPlace, SideBits::eBottom | SideBits::eRight, 116 ScrollDirections()})); 117 } 118 119 // overscroll-behavior: none, none. 120 UpdateOverscrollBehavior(OverscrollBehavior::None, 121 OverscrollBehavior::None); 122 { 123 QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID, 124 {CompositorHitTestFlags::eVisibleToHitTest, 125 CompositorHitTestFlags::eIrregularArea}); 126 auto [result, delayedHandledResult] = 127 TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag); 128 EXPECT_EQ(result.GetHandledResult(), Nothing()); 129 EXPECT_EQ( 130 delayedHandledResult, 131 (APZHandledResult{expectedPlace, SideBits::eBottom | SideBits::eRight, 132 ScrollDirections()})); 133 } 134 135 // overscroll-behavior: auto, none. 136 UpdateOverscrollBehavior(OverscrollBehavior::Auto, 137 OverscrollBehavior::None); 138 { 139 QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID, 140 {CompositorHitTestFlags::eVisibleToHitTest, 141 CompositorHitTestFlags::eIrregularArea}); 142 auto [result, delayedHandledResult] = 143 TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag); 144 EXPECT_EQ(result.GetHandledResult(), Nothing()); 145 EXPECT_EQ( 146 delayedHandledResult, 147 (APZHandledResult{expectedPlace, SideBits::eBottom | SideBits::eRight, 148 HorizontalScrollDirection})); 149 } 150 151 // overscroll-behavior: none, auto. 152 UpdateOverscrollBehavior(OverscrollBehavior::None, 153 OverscrollBehavior::Auto); 154 { 155 QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID, 156 {CompositorHitTestFlags::eVisibleToHitTest, 157 CompositorHitTestFlags::eIrregularArea}); 158 auto [result, delayedHandledResult] = 159 TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag); 160 EXPECT_EQ(result.GetHandledResult(), Nothing()); 161 EXPECT_EQ( 162 delayedHandledResult, 163 (APZHandledResult{expectedPlace, SideBits::eBottom | SideBits::eRight, 164 VerticalScrollDirection})); 165 } 166 } 167 168 void ScrollableDirectionsWithEventHandlerTest( 169 PreventDefaultFlag aPreventDefaultFlag) { 170 UpdateHitTestingTree(); 171 172 APZHandledPlace expectedPlace = 173 aPreventDefaultFlag == PreventDefaultFlag::No 174 ? APZHandledPlace::HandledByRoot 175 : APZHandledPlace::HandledByContent; 176 { 177 QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID, 178 {CompositorHitTestFlags::eVisibleToHitTest, 179 CompositorHitTestFlags::eIrregularArea}); 180 auto [result, delayedHandledResult] = 181 TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag); 182 EXPECT_EQ(result.GetHandledResult(), Nothing()); 183 EXPECT_EQ( 184 delayedHandledResult, 185 (APZHandledResult{expectedPlace, SideBits::eBottom | SideBits::eRight, 186 EitherScrollDirection})); 187 } 188 189 // scroll down a bit. 190 SetScrollOffsetOnMainThread(CSSPoint(0, 10)); 191 { 192 QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID, 193 {CompositorHitTestFlags::eVisibleToHitTest, 194 CompositorHitTestFlags::eIrregularArea}); 195 auto [result, delayedHandledResult] = 196 TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag); 197 EXPECT_EQ(result.GetHandledResult(), Nothing()); 198 EXPECT_EQ(delayedHandledResult, 199 (APZHandledResult{ 200 expectedPlace, 201 SideBits::eTop | SideBits::eBottom | SideBits::eRight, 202 EitherScrollDirection})); 203 } 204 205 // scroll to the bottom edge 206 SetScrollOffsetOnMainThread(CSSPoint(0, 100)); 207 { 208 QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID, 209 {CompositorHitTestFlags::eVisibleToHitTest, 210 CompositorHitTestFlags::eIrregularArea}); 211 auto [result, delayedHandledResult] = 212 TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag); 213 EXPECT_EQ(result.GetHandledResult(), Nothing()); 214 EXPECT_EQ( 215 delayedHandledResult, 216 (APZHandledResult{expectedPlace, SideBits::eRight | SideBits::eTop, 217 EitherScrollDirection})); 218 } 219 220 // scroll to right a bit. 221 SetScrollOffsetOnMainThread(CSSPoint(10, 100)); 222 { 223 QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID, 224 {CompositorHitTestFlags::eVisibleToHitTest, 225 CompositorHitTestFlags::eIrregularArea}); 226 auto [result, delayedHandledResult] = 227 TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag); 228 EXPECT_EQ(result.GetHandledResult(), Nothing()); 229 EXPECT_EQ( 230 delayedHandledResult, 231 (APZHandledResult{expectedPlace, 232 SideBits::eLeft | SideBits::eRight | SideBits::eTop, 233 EitherScrollDirection})); 234 } 235 236 // scroll to the right edge. 237 SetScrollOffsetOnMainThread(CSSPoint(100, 100)); 238 { 239 QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID, 240 {CompositorHitTestFlags::eVisibleToHitTest, 241 CompositorHitTestFlags::eIrregularArea}); 242 auto [result, delayedHandledResult] = 243 TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag); 244 EXPECT_EQ(result.GetHandledResult(), Nothing()); 245 EXPECT_EQ( 246 delayedHandledResult, 247 (APZHandledResult{expectedPlace, SideBits::eTop | SideBits::eLeft, 248 EitherScrollDirection})); 249 } 250 } 251 }; 252 253 TEST_F(APZEventResultTester, OverscrollDirections) { 254 CreateScrollableRootLayer(); 255 256 TimeDuration tapDuration = TimeDuration::FromMilliseconds(100); 257 258 // The default value of overscroll-behavior is auto. 259 APZEventResult result = Tap(manager, ScreenIntPoint(50, 50), tapDuration); 260 EXPECT_EQ(result.GetHandledResult()->mOverscrollDirections, 261 EitherScrollDirection); 262 263 // overscroll-behavior: contain, contain. 264 UpdateOverscrollBehavior(OverscrollBehavior::Contain, 265 OverscrollBehavior::Contain); 266 result = Tap(manager, ScreenIntPoint(50, 50), tapDuration); 267 EXPECT_EQ(result.GetHandledResult()->mOverscrollDirections, 268 ScrollDirections()); 269 270 // overscroll-behavior: none, none. 271 UpdateOverscrollBehavior(OverscrollBehavior::None, OverscrollBehavior::None); 272 result = Tap(manager, ScreenIntPoint(50, 50), tapDuration); 273 EXPECT_EQ(result.GetHandledResult()->mOverscrollDirections, 274 ScrollDirections()); 275 276 // overscroll-behavior: auto, none. 277 UpdateOverscrollBehavior(OverscrollBehavior::Auto, OverscrollBehavior::None); 278 result = Tap(manager, ScreenIntPoint(50, 50), tapDuration); 279 EXPECT_EQ(result.GetHandledResult()->mOverscrollDirections, 280 HorizontalScrollDirection); 281 282 // overscroll-behavior: none, auto. 283 UpdateOverscrollBehavior(OverscrollBehavior::None, OverscrollBehavior::Auto); 284 result = Tap(manager, ScreenIntPoint(50, 50), tapDuration); 285 EXPECT_EQ(result.GetHandledResult()->mOverscrollDirections, 286 VerticalScrollDirection); 287 } 288 289 TEST_F(APZEventResultTester, ScrollableDirections) { 290 CreateScrollableRootLayer(); 291 292 TimeDuration tapDuration = TimeDuration::FromMilliseconds(100); 293 294 APZEventResult result = Tap(manager, ScreenIntPoint(50, 50), tapDuration); 295 // scrollable to down/right. 296 EXPECT_EQ(result.GetHandledResult()->mScrollableDirections, 297 SideBits::eBottom | SideBits::eRight); 298 299 // scroll down a bit. 300 SetScrollOffsetOnMainThread(CSSPoint(0, 10)); 301 result = Tap(manager, ScreenIntPoint(50, 50), tapDuration); 302 // also scrollable toward top. 303 EXPECT_EQ(result.GetHandledResult()->mScrollableDirections, 304 SideBits::eTop | SideBits::eBottom | SideBits::eRight); 305 306 // scroll to the bottom edge 307 SetScrollOffsetOnMainThread(CSSPoint(0, 100)); 308 result = Tap(manager, ScreenIntPoint(50, 50), tapDuration); 309 EXPECT_EQ(result.GetHandledResult()->mScrollableDirections, 310 SideBits::eRight | SideBits::eTop); 311 312 // scroll to right a bit. 313 SetScrollOffsetOnMainThread(CSSPoint(10, 100)); 314 result = Tap(manager, ScreenIntPoint(50, 50), tapDuration); 315 EXPECT_EQ(result.GetHandledResult()->mScrollableDirections, 316 SideBits::eLeft | SideBits::eRight | SideBits::eTop); 317 318 // scroll to the right edge. 319 SetScrollOffsetOnMainThread(CSSPoint(100, 100)); 320 result = Tap(manager, ScreenIntPoint(50, 50), tapDuration); 321 EXPECT_EQ(result.GetHandledResult()->mScrollableDirections, 322 SideBits::eLeft | SideBits::eTop); 323 } 324 325 class APZEventResultTesterMock : public APZEventResultTester { 326 public: 327 APZEventResultTesterMock() { CreateMockHitTester(); } 328 }; 329 330 TEST_F(APZEventResultTesterMock, OverscrollDirectionsWithEventHandler) { 331 CreateScrollableRootLayer(); 332 333 OverscrollDirectionsWithEventHandlerTest(PreventDefaultFlag::No); 334 } 335 336 TEST_F(APZEventResultTesterMock, 337 OverscrollDirectionsWithPreventDefaultEventHandler) { 338 CreateScrollableRootLayer(); 339 340 OverscrollDirectionsWithEventHandlerTest(PreventDefaultFlag::Yes); 341 } 342 343 TEST_F(APZEventResultTesterMock, ScrollableDirectionsWithEventHandler) { 344 CreateScrollableRootLayer(); 345 346 ScrollableDirectionsWithEventHandlerTest(PreventDefaultFlag::No); 347 } 348 349 TEST_F(APZEventResultTesterMock, 350 ScrollableDirectionsWithPreventDefaultEventHandler) { 351 CreateScrollableRootLayer(); 352 353 ScrollableDirectionsWithEventHandlerTest(PreventDefaultFlag::Yes); 354 } 355 356 // Test that APZEventResult::GetHandledResult() is correctly 357 // populated. 358 TEST_F(APZEventResultTesterMock, HandledByRootApzcFlag) { 359 // Create simple layer tree containing a dispatch-to-content region 360 // that covers part but not all of its area. 361 const char* treeShape = "x"; 362 LayerIntRect layerVisibleRects[] = { 363 LayerIntRect(0, 0, 100, 100), 364 }; 365 CreateScrollData(treeShape, layerVisibleRects); 366 SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, 367 CSSRect(0, 0, 100, 200)); 368 ModifyFrameMetrics(root, [](ScrollMetadata& sm, FrameMetrics& metrics) { 369 metrics.SetIsRootContent(true); 370 }); 371 // away from the scrolling container layer. 372 registration = MakeUnique<ScopedLayerTreeRegistration>(LayersId{0}, mcc); 373 UpdateHitTestingTree(); 374 375 // Tap the top half and check that we report that the event was 376 // handled by the root APZC. 377 QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); 378 APZEventResult result = 379 TouchDown(manager, ScreenIntPoint(50, 25), mcc->Time()); 380 TouchUp(manager, ScreenIntPoint(50, 25), mcc->Time()); 381 EXPECT_EQ(result.GetHandledResult(), 382 Some(APZHandledResult{APZHandledPlace::HandledByRoot, 383 SideBits::eBottom, EitherScrollDirection})); 384 385 // Tap the bottom half and check that we report that we're not 386 // sure whether the event was handled by the root APZC. 387 QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID, 388 {CompositorHitTestFlags::eVisibleToHitTest, 389 CompositorHitTestFlags::eIrregularArea}); 390 result = TouchDown(manager, ScreenIntPoint(50, 75), mcc->Time()); 391 TouchUp(manager, ScreenIntPoint(50, 75), mcc->Time()); 392 EXPECT_EQ(result.GetHandledResult(), Nothing()); 393 394 // Register an input block callback that will tell us the 395 // delayed answer. 396 APZHandledResult delayedAnswer{APZHandledPlace::Invalid, SideBits::eNone, 397 ScrollDirections()}; 398 manager->AddInputBlockCallback( 399 result.mInputBlockId, [&](uint64_t id, const APZHandledResult& answer) { 400 EXPECT_EQ(id, result.mInputBlockId); 401 delayedAnswer = answer; 402 }); 403 404 // Send APZ the relevant notifications to allow it to process the 405 // input block. 406 manager->SetAllowedTouchBehavior(result.mInputBlockId, 407 {AllowedTouchBehavior::VERTICAL_PAN}); 408 manager->SetTargetAPZC(result.mInputBlockId, {result.mTargetGuid}); 409 manager->ContentReceivedInputBlock(result.mInputBlockId, 410 /*aPreventDefault=*/false); 411 412 // Check that we received the delayed answer and it is what we expect. 413 EXPECT_EQ(delayedAnswer, 414 (APZHandledResult{APZHandledPlace::HandledByRoot, SideBits::eBottom, 415 EitherScrollDirection})); 416 417 // Now repeat the tap on the bottom half, but simulate a prevent-default. 418 // This time, we expect a delayed answer of `HandledByContent`. 419 QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID, 420 {CompositorHitTestFlags::eVisibleToHitTest, 421 CompositorHitTestFlags::eIrregularArea}); 422 result = TouchDown(manager, ScreenIntPoint(50, 75), mcc->Time()); 423 TouchUp(manager, ScreenIntPoint(50, 75), mcc->Time()); 424 EXPECT_EQ(result.GetHandledResult(), Nothing()); 425 manager->AddInputBlockCallback( 426 result.mInputBlockId, [&](uint64_t id, const APZHandledResult& answer) { 427 EXPECT_EQ(id, result.mInputBlockId); 428 delayedAnswer = answer; 429 }); 430 manager->SetAllowedTouchBehavior(result.mInputBlockId, 431 {AllowedTouchBehavior::VERTICAL_PAN}); 432 manager->SetTargetAPZC(result.mInputBlockId, {result.mTargetGuid}); 433 manager->ContentReceivedInputBlock(result.mInputBlockId, 434 /*aPreventDefault=*/true); 435 EXPECT_EQ(delayedAnswer, 436 (APZHandledResult{APZHandledPlace::HandledByContent, 437 SideBits::eBottom, EitherScrollDirection})); 438 439 // Shrink the scrollable area, now it's no longer scrollable. 440 ModifyFrameMetrics(root, [](ScrollMetadata& sm, FrameMetrics& metrics) { 441 metrics.SetScrollableRect(CSSRect(0, 0, 100, 100)); 442 }); 443 UpdateHitTestingTree(); 444 // Now repeat the tap on the bottom half with an event handler. 445 // This time, we expect a delayed answer of `Unhandled`. 446 QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID, 447 {CompositorHitTestFlags::eVisibleToHitTest, 448 CompositorHitTestFlags::eIrregularArea}); 449 result = TouchDown(manager, ScreenIntPoint(50, 75), mcc->Time()); 450 TouchUp(manager, ScreenIntPoint(50, 75), mcc->Time()); 451 EXPECT_EQ(result.GetHandledResult(), Nothing()); 452 manager->AddInputBlockCallback( 453 result.mInputBlockId, [&](uint64_t id, const APZHandledResult& answer) { 454 EXPECT_EQ(id, result.mInputBlockId); 455 delayedAnswer = answer; 456 }); 457 manager->SetAllowedTouchBehavior(result.mInputBlockId, 458 {AllowedTouchBehavior::VERTICAL_PAN}); 459 manager->SetTargetAPZC(result.mInputBlockId, {result.mTargetGuid}); 460 manager->ContentReceivedInputBlock(result.mInputBlockId, 461 /*aPreventDefault=*/false); 462 EXPECT_EQ(delayedAnswer, 463 (APZHandledResult{APZHandledPlace::Unhandled, SideBits::eNone, 464 EitherScrollDirection})); 465 466 // Repeat the tap on the bottom half, with no event handler. 467 // Make sure we get an eager answer of `Unhandled`. 468 QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID); 469 result = TouchDown(manager, ScreenIntPoint(50, 75), mcc->Time()); 470 TouchUp(manager, ScreenIntPoint(50, 75), mcc->Time()); 471 EXPECT_EQ(result.GetStatus(), nsEventStatus_eIgnore); 472 EXPECT_EQ(result.GetHandledResult(), 473 Some(APZHandledResult{APZHandledPlace::Unhandled, SideBits::eNone, 474 EitherScrollDirection})); 475 }