InputQueue.cpp (49236B)
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 "InputQueue.h" 8 9 #include <inttypes.h> 10 11 #include "AsyncPanZoomController.h" 12 13 #include "GestureEventListener.h" 14 #include "InputBlockState.h" 15 #include "mozilla/Assertions.h" 16 #include "mozilla/EventForwards.h" 17 #include "mozilla/layers/APZInputBridge.h" 18 #include "mozilla/layers/APZThreadUtils.h" 19 #include "mozilla/RefPtr.h" 20 #include "mozilla/ToString.h" 21 #include "OverscrollHandoffState.h" 22 #include "QueuedInput.h" 23 #include "mozilla/StaticPrefs_apz.h" 24 #include "mozilla/StaticPrefs_layout.h" 25 #include "mozilla/StaticPrefs_ui.h" 26 27 static mozilla::LazyLogModule sApzInpLog("apz.inputqueue"); 28 #define INPQ_LOG(...) MOZ_LOG(sApzInpLog, LogLevel::Debug, (__VA_ARGS__)) 29 #define INPQ_LOG_TEST() MOZ_LOG_TEST(sApzInpLog, LogLevel::Debug) 30 31 namespace mozilla { 32 namespace layers { 33 34 InputQueue::InputQueue() = default; 35 36 InputQueue::~InputQueue() { mQueuedInputs.Clear(); } 37 38 APZEventResult InputQueue::ReceiveInputEvent( 39 const RefPtr<AsyncPanZoomController>& aTarget, 40 TargetConfirmationFlags aFlags, InputData& aEvent, 41 const Maybe<nsTArray<TouchBehaviorFlags>>& aTouchBehaviors) { 42 APZThreadUtils::AssertOnControllerThread(); 43 44 AutoRunImmediateTimeout timeoutRunner{this}; 45 46 switch (aEvent.mInputType) { 47 case MULTITOUCH_INPUT: { 48 const MultiTouchInput& event = aEvent.AsMultiTouchInput(); 49 return ReceiveTouchInput(aTarget, aFlags, event, aTouchBehaviors); 50 } 51 52 case SCROLLWHEEL_INPUT: { 53 const ScrollWheelInput& event = aEvent.AsScrollWheelInput(); 54 return ReceiveScrollWheelInput(aTarget, aFlags, event); 55 } 56 57 case PANGESTURE_INPUT: { 58 const PanGestureInput& event = aEvent.AsPanGestureInput(); 59 return ReceivePanGestureInput(aTarget, aFlags, event); 60 } 61 62 case PINCHGESTURE_INPUT: { 63 const PinchGestureInput& event = aEvent.AsPinchGestureInput(); 64 return ReceivePinchGestureInput(aTarget, aFlags, event); 65 } 66 67 case MOUSE_INPUT: { 68 MouseInput& event = aEvent.AsMouseInput(); 69 return ReceiveMouseInput(aTarget, aFlags, event); 70 } 71 72 case KEYBOARD_INPUT: { 73 // Every keyboard input must have a confirmed target 74 MOZ_ASSERT(aTarget && aFlags.mTargetConfirmed); 75 76 const KeyboardInput& event = aEvent.AsKeyboardInput(); 77 return ReceiveKeyboardInput(aTarget, aFlags, event); 78 } 79 80 default: { 81 // The `mStatus` for other input type is only used by tests, so just 82 // pass through the return value of HandleInputEvent() for now. 83 APZEventResult result(aTarget, aFlags); 84 nsEventStatus status = 85 aTarget->HandleInputEvent(aEvent, aTarget->GetTransformToThis()); 86 switch (status) { 87 case nsEventStatus_eIgnore: 88 result.SetStatusAsIgnore(); 89 break; 90 case nsEventStatus_eConsumeNoDefault: 91 result.SetStatusAsConsumeNoDefault(); 92 break; 93 case nsEventStatus_eConsumeDoDefault: 94 result.SetStatusAsConsumeDoDefault(aTarget); 95 break; 96 default: 97 MOZ_ASSERT_UNREACHABLE("An invalid status"); 98 break; 99 } 100 return result; 101 } 102 } 103 } 104 105 APZEventResult InputQueue::ReceiveTouchInput( 106 const RefPtr<AsyncPanZoomController>& aTarget, 107 TargetConfirmationFlags aFlags, const MultiTouchInput& aEvent, 108 const Maybe<nsTArray<TouchBehaviorFlags>>& aTouchBehaviors) { 109 APZEventResult result(aTarget, aFlags); 110 111 RefPtr<TouchBlockState> block; 112 bool waitingForContentResponse = false; 113 if (aEvent.mType == MultiTouchInput::MULTITOUCH_START) { 114 nsTArray<TouchBehaviorFlags> currentBehaviors; 115 bool haveBehaviors = false; 116 if (mActiveTouchBlock) { 117 haveBehaviors = 118 mActiveTouchBlock->GetAllowedTouchBehaviors(currentBehaviors); 119 // If the behaviours aren't set, but the main-thread response timer on 120 // the block is expired we still treat it as though it has behaviors, 121 // because in that case we still want to interrupt the fast-fling and 122 // use the default behaviours. 123 haveBehaviors |= mActiveTouchBlock->IsContentResponseTimerExpired(); 124 } 125 126 block = StartNewTouchBlock(aTarget, aFlags); 127 INPQ_LOG("started new touch block %p id %" PRIu64 " for target %p\n", 128 block.get(), block->GetBlockId(), aTarget.get()); 129 130 // XXX using the chain from |block| here may be wrong in cases where the 131 // target isn't confirmed and the real target turns out to be something 132 // else. For now assume this is rare enough that it's not an issue. 133 if (mQueuedInputs.IsEmpty() && aEvent.mTouches.Length() == 1 && 134 block->GetOverscrollHandoffChain()->HasFastFlungApzc() && 135 haveBehaviors) { 136 // If we're already in a fast fling, and a single finger goes down, then 137 // we want special handling for the touch event, because it shouldn't get 138 // delivered to content. Note that we don't set this flag when going 139 // from a fast fling to a pinch state (i.e. second finger goes down while 140 // the first finger is moving). 141 block->SetDuringFastFling(); 142 block->SetConfirmedTargetApzc( 143 aTarget, InputBlockState::TargetConfirmationState::eConfirmed, 144 InputQueueIterator() /* the block was just created so it has no events 145 */ 146 , 147 false /* not a scrollbar drag */); 148 block->SetAllowedTouchBehaviors(currentBehaviors); 149 INPQ_LOG("block %p tagged as fast-motion\n", block.get()); 150 } else if (aTouchBehaviors) { 151 // If this block isn't started during a fast-fling, and APZCTM has 152 // provided touch behavior information, then put it on the block so 153 // that the ArePointerEventsConsumable call below can use it. 154 block->SetAllowedTouchBehaviors(*aTouchBehaviors); 155 } 156 157 CancelAnimationsForNewBlock(block); 158 159 waitingForContentResponse = MaybeRequestContentResponse(aTarget, block); 160 } else { 161 // for touch inputs that don't start a block, APZCTM shouldn't be giving 162 // us any touch behaviors. 163 MOZ_ASSERT(aTouchBehaviors.isNothing()); 164 165 // If the active touch block is for a long tap, add new touch events into 166 // the original touch block, to ensure that they're only processed if the 167 // original touch block is not prevented. 168 block = mActiveTouchBlock && mActiveTouchBlock->ForLongTap() 169 ? mPrevActiveTouchBlock.get() 170 : mActiveTouchBlock.get(); 171 if (!block) { 172 NS_WARNING( 173 "Received a non-start touch event while no touch blocks active!"); 174 return result; 175 } 176 177 INPQ_LOG("received new touch event (type=%d) in block %p\n", aEvent.mType, 178 block.get()); 179 } 180 181 result.mInputBlockId = block->GetBlockId(); 182 183 // Note that the |aTarget| the APZCTM sent us may contradict the confirmed 184 // target set on the block. In this case the confirmed target (which may be 185 // null) should take priority. This is equivalent to just always using the 186 // target (confirmed or not) from the block. 187 RefPtr<AsyncPanZoomController> target = block->GetTargetApzc(); 188 189 // XXX calling ArePointerEventsConsumable on |target| may be wrong here if 190 // the target isn't confirmed and the real target turns out to be something 191 // else. For now assume this is rare enough that it's not an issue. 192 PointerEventsConsumableFlags consumableFlags; 193 if (target) { 194 consumableFlags = target->ArePointerEventsConsumable(block, aEvent); 195 } 196 if (block->IsDuringFastFling()) { 197 INPQ_LOG("dropping event due to block %p being in fast motion\n", 198 block.get()); 199 result.SetStatusForFastFling(*block, aFlags, consumableFlags, target); 200 } else { // handling depends on ArePointerEventsConsumable() 201 bool consumable = consumableFlags.IsConsumable(); 202 const bool wasInSlop = block->IsInSlop(); 203 if (block->UpdateSlopState(aEvent, consumable)) { 204 INPQ_LOG("dropping event due to block %p being in %sslop\n", block.get(), 205 consumable ? "" : "mini-"); 206 result.SetStatusAsConsumeNoDefault(); 207 } else { 208 // If all following conditions are met, we need to wait for a content 209 // response (again); 210 // 1) this is the first touch-move event bailing out from in-slop state 211 // after a long-tap event has been fired 212 // 2) there's any APZ-aware event listeners 213 // 3) the event block hasn't yet been prevented 214 // 215 // An example scenario; 216 // in the content there are two event listeners for `touchstart` and 217 // `touchmove` respectively, and doing `preventDefault()` in the 218 // `touchmove` event listener. Then if the user kept touching at a point 219 // until a long-tap event happens, then if the user started moving their 220 // finger, we have to wait for a content response twice, one is for 221 // `touchstart` and one is for `touchmove`. 222 if (wasInSlop && aEvent.mType == MultiTouchInput::MULTITOUCH_MOVE && 223 (block->WasLongTapProcessed() || block->IsWaitingLongTapResult()) && 224 !block->IsTargetOriginallyConfirmed() && !block->ShouldDropEvents()) { 225 INPQ_LOG( 226 "bailing out from in-stop state in block %p after a long-tap " 227 "happened\n", 228 block.get()); 229 block->ResetContentResponseTimerExpired(); 230 ScheduleMainThreadTimeout(aTarget, block); 231 } 232 block->SetNeedsToWaitTouchMove(false); 233 result.SetStatusForTouchEvent(*block, aFlags, consumableFlags, target); 234 } 235 } 236 mQueuedInputs.AppendElement(MakeUnique<QueuedInput>(aEvent, *block)); 237 ProcessQueue(); 238 239 // If this block just started and is waiting for a content response, but 240 // also in a slop state (i.e. touchstart gets delivered to content but 241 // not any touchmoves), then we might end up in a situation where we don't 242 // get the content response until the timeout is hit because we never exit 243 // the slop state. But if that timeout is longer than the long-press timeout, 244 // then the long-press gets delayed too. Avoid that by scheduling a callback 245 // with the long-press timeout that will force the block to get processed. 246 int32_t longTapTimeout = StaticPrefs::ui_click_hold_context_menus_delay(); 247 int32_t contentTimeout = StaticPrefs::apz_content_response_timeout(); 248 if (waitingForContentResponse && longTapTimeout < contentTimeout && 249 block->IsInSlop() && GestureEventListener::IsLongTapEnabled()) { 250 MOZ_ASSERT(aEvent.mType == MultiTouchInput::MULTITOUCH_START); 251 MOZ_ASSERT(!block->IsDuringFastFling()); 252 RefPtr<Runnable> maybeLongTap = NewRunnableMethod<uint64_t>( 253 "layers::InputQueue::MaybeLongTapTimeout", this, 254 &InputQueue::MaybeLongTapTimeout, block->GetBlockId()); 255 INPQ_LOG("scheduling maybe-long-tap timeout for target %p\n", 256 aTarget.get()); 257 aTarget->PostDelayedTask(maybeLongTap.forget(), longTapTimeout); 258 } 259 260 return result; 261 } 262 263 APZEventResult InputQueue::ReceiveMouseInput( 264 const RefPtr<AsyncPanZoomController>& aTarget, 265 TargetConfirmationFlags aFlags, MouseInput& aEvent) { 266 APZEventResult result(aTarget, aFlags); 267 268 // On a new mouse down we can have a new target so we must force a new block 269 // with a new target. 270 bool newBlock = DragTracker::StartsDrag(aEvent); 271 272 RefPtr<DragBlockState> block = newBlock ? nullptr : mActiveDragBlock.get(); 273 if (block && block->HasReceivedMouseUp()) { 274 block = nullptr; 275 } 276 277 if (!block && mDragTracker.InDrag()) { 278 // If there's no current drag block, but we're getting a move with a button 279 // down, we need to start a new drag block because we're obviously already 280 // in the middle of a drag (it probably got interrupted by something else). 281 INPQ_LOG( 282 "got a drag event outside a drag block, need to create a block to hold " 283 "it\n"); 284 newBlock = true; 285 } 286 287 mDragTracker.Update(aEvent); 288 289 if (!newBlock && !block) { 290 // This input event is not in a drag block, so we're not doing anything 291 // with it, return eIgnore. 292 return result; 293 } 294 295 if (!block) { 296 MOZ_ASSERT(newBlock); 297 block = new DragBlockState(aTarget, aFlags, aEvent); 298 299 INPQ_LOG( 300 "started new drag block %p id %" PRIu64 301 " " 302 "for %sconfirmed target %p; on scrollbar: %d; on scrollthumb: %d\n", 303 block.get(), block->GetBlockId(), aFlags.mTargetConfirmed ? "" : "un", 304 aTarget.get(), aFlags.mHitScrollbar, aFlags.mHitScrollThumb); 305 306 mActiveDragBlock = block; 307 308 if (aFlags.mHitScrollThumb || !aFlags.mHitScrollbar) { 309 // If we're running autoscroll, we'll always cancel it during the 310 // following call of CancelAnimationsForNewBlock. At this time, 311 // we don't want to fire `click` event on the web content for web-compat 312 // with Chrome. Therefore, we notify widget of it with the flag. 313 if ((aEvent.mType == MouseInput::MOUSE_DOWN || 314 aEvent.mType == MouseInput::MOUSE_UP) && 315 block->GetOverscrollHandoffChain()->HasAutoscrollApzc()) { 316 aEvent.mPreventClickEvent = true; 317 } 318 CancelAnimationsForNewBlock(block); 319 } 320 MaybeRequestContentResponse(aTarget, block); 321 } 322 323 result.mInputBlockId = block->GetBlockId(); 324 325 mQueuedInputs.AppendElement(MakeUnique<QueuedInput>(aEvent, *block)); 326 ProcessQueue(); 327 328 if (DragTracker::EndsDrag(aEvent)) { 329 block->MarkMouseUpReceived(); 330 } 331 332 // The event is part of a drag block and could potentially cause 333 // scrolling, so return DoDefault. 334 result.SetStatusAsConsumeDoDefault(*block); 335 return result; 336 } 337 338 APZEventResult InputQueue::ReceiveScrollWheelInput( 339 const RefPtr<AsyncPanZoomController>& aTarget, 340 TargetConfirmationFlags aFlags, const ScrollWheelInput& aEvent) { 341 APZEventResult result(aTarget, aFlags); 342 343 RefPtr<WheelBlockState> block = mActiveWheelBlock.get(); 344 // If the block is not accepting new events we'll create a new input block 345 // (and therefore a new wheel transaction). 346 if (block && 347 (!block->ShouldAcceptNewEvent() || block->MaybeTimeout(aEvent))) { 348 block = nullptr; 349 } 350 351 MOZ_ASSERT(!block || block->InTransaction()); 352 353 if (!block) { 354 block = new WheelBlockState(aTarget, aFlags, aEvent); 355 INPQ_LOG("started new scroll wheel block %p id %" PRIu64 356 " for %starget %p\n", 357 block.get(), block->GetBlockId(), 358 aFlags.mTargetConfirmed ? "confirmed " : "", aTarget.get()); 359 360 mActiveWheelBlock = block; 361 362 MaybeRequestContentResponse(aTarget, block); 363 } else { 364 INPQ_LOG("received new wheel event in block %p\n", block.get()); 365 } 366 367 result.mInputBlockId = block->GetBlockId(); 368 369 // Note that the |aTarget| the APZCTM sent us may contradict the confirmed 370 // target set on the block. In this case the confirmed target (which may be 371 // null) should take priority. This is equivalent to just always using the 372 // target (confirmed or not) from the block, which is what 373 // ProcessQueue() does. 374 mQueuedInputs.AppendElement(MakeUnique<QueuedInput>(aEvent, *block)); 375 376 // The WheelBlockState needs to affix a counter to the event before we process 377 // it. Note that the counter is affixed to the copy in the queue rather than 378 // |aEvent|. 379 block->Update(mQueuedInputs.LastElement()->Input()->AsScrollWheelInput()); 380 381 ProcessQueue(); 382 383 result.SetStatusAsConsumeDoDefault(*block); 384 return result; 385 } 386 387 APZEventResult InputQueue::ReceiveKeyboardInput( 388 const RefPtr<AsyncPanZoomController>& aTarget, 389 TargetConfirmationFlags aFlags, const KeyboardInput& aEvent) { 390 APZEventResult result(aTarget, aFlags); 391 392 RefPtr<KeyboardBlockState> block = mActiveKeyboardBlock.get(); 393 394 // If the block is targeting a different Apzc than this keyboard event then 395 // we'll create a new input block 396 if (block && block->GetTargetApzc() != aTarget) { 397 block = nullptr; 398 } 399 400 if (!block) { 401 block = new KeyboardBlockState(aTarget); 402 INPQ_LOG("started new keyboard block %p id %" PRIu64 " for target %p\n", 403 block.get(), block->GetBlockId(), aTarget.get()); 404 405 mActiveKeyboardBlock = block; 406 } else { 407 INPQ_LOG("received new keyboard event in block %p\n", block.get()); 408 } 409 410 result.mInputBlockId = block->GetBlockId(); 411 412 mQueuedInputs.AppendElement(MakeUnique<QueuedInput>(aEvent, *block)); 413 414 ProcessQueue(); 415 416 // If APZ is allowing passive listeners then we must dispatch the event to 417 // content, otherwise we can consume the event. 418 if (StaticPrefs::apz_keyboard_passive_listeners()) { 419 result.SetStatusAsConsumeDoDefault(*block); 420 } else { 421 result.SetStatusAsConsumeNoDefault(); 422 } 423 return result; 424 } 425 426 static bool CanScrollTargetHorizontally(const PanGestureInput& aInitialEvent, 427 PanGestureBlockState* aBlock) { 428 PanGestureInput horizontalComponent = aInitialEvent; 429 horizontalComponent.mPanDisplacement.y = 0; 430 ScrollDirections allowedScrollDirections; 431 RefPtr<AsyncPanZoomController> horizontallyScrollableAPZC = 432 aBlock->GetOverscrollHandoffChain()->FindFirstScrollable( 433 horizontalComponent, &allowedScrollDirections, 434 OverscrollHandoffChain::IncludeOverscroll::No); 435 return horizontallyScrollableAPZC && 436 horizontallyScrollableAPZC == aBlock->GetTargetApzc() && 437 allowedScrollDirections.contains(ScrollDirection::eHorizontal); 438 } 439 440 APZEventResult InputQueue::ReceivePanGestureInput( 441 const RefPtr<AsyncPanZoomController>& aTarget, 442 TargetConfirmationFlags aFlags, const PanGestureInput& aEvent) { 443 APZEventResult result(aTarget, aFlags); 444 445 if (aEvent.mType == PanGestureInput::PANGESTURE_INTERRUPTED) { 446 if (RefPtr<PanGestureBlockState> block = mActivePanGestureBlock.get()) { 447 mQueuedInputs.AppendElement(MakeUnique<QueuedInput>(aEvent, *block)); 448 ProcessQueue(); 449 } 450 result.SetStatusAsIgnore(); 451 return result; 452 } 453 454 bool startsNewBlock = aEvent.mType == PanGestureInput::PANGESTURE_MAYSTART || 455 aEvent.mType == PanGestureInput::PANGESTURE_START; 456 457 RefPtr<PanGestureBlockState> block; 458 if (!startsNewBlock) { 459 block = mActivePanGestureBlock.get(); 460 } 461 462 PanGestureInput event = aEvent; 463 464 // Below `SetStatusAsConsumeDoDefault()` preserves `mHandledResult` of 465 // `result` which was set in the ctor of APZEventResult at the top of this 466 // function based on `aFlag` so that the `mHandledResult` value is reliable to 467 // tell whether the event will be handled by the root content APZC at least 468 // for swipe-navigation stuff. E.g. if a pan-start event scrolled the root 469 // scroll container, we don't need to anything for swipe-navigation. 470 result.SetStatusAsConsumeDoDefault(); 471 472 if (!block || block->WasInterrupted()) { 473 if (event.mType == PanGestureInput::PANGESTURE_MOMENTUMSTART || 474 event.mType == PanGestureInput::PANGESTURE_MOMENTUMPAN || 475 event.mType == PanGestureInput::PANGESTURE_MOMENTUMEND) { 476 // If there are momentum events after an interruption, discard them. 477 // However, if there is a non-momentum event (indicating the user 478 // continued scrolling on the touchpad), a new input block is started 479 // by turning the event into a pan-start below. 480 return result; 481 } 482 if (!startsNewBlock) { 483 // Only PANGESTURE_MAYSTART or PANGESTURE_START events are allowed to 484 // start a new pan gesture block, but we really want to start a new block 485 // here, so we magically turn this input into a PANGESTURE_START. 486 INPQ_LOG( 487 "transmogrifying pan input %d to PANGESTURE_START for new block\n", 488 event.mType); 489 event.mType = PanGestureInput::PANGESTURE_START; 490 } 491 block = new PanGestureBlockState(aTarget, aFlags, event); 492 INPQ_LOG("started new pan gesture block %p id %" PRIu64 " for target %p\n", 493 block.get(), block->GetBlockId(), aTarget.get()); 494 495 if (event.mType == PanGestureInput::PANGESTURE_MAYSTART) { 496 block->ConfirmForHoldGesture(); 497 } 498 499 mActivePanGestureBlock = block; 500 501 const bool waitingForContentResponse = 502 MaybeRequestContentResponse(aTarget, block); 503 504 if (event.AllowsSwipe() && !CanScrollTargetHorizontally(event, block)) { 505 // We will ask the browser whether this pan event is going to be used for 506 // swipe or not, so we need to wait the response. 507 block->SetNeedsToWaitForBrowserGestureResponse(true); 508 if (!waitingForContentResponse) { 509 ScheduleMainThreadTimeout(aTarget, block); 510 } 511 if (aFlags.mTargetConfirmed) { 512 // This event may trigger a swipe gesture, depending on what our caller 513 // wants to do it. We need to suspend handling of this block until we 514 // get a content response which will tell us whether to proceed or abort 515 // the block. 516 block->SetNeedsToWaitForContentResponse(true); 517 518 // Inform our caller that we haven't scrolled in response to the event 519 // and that a swipe can be started from this event if desired. 520 result.SetStatusAsIgnore(); 521 } 522 } 523 } else { 524 INPQ_LOG("received new pan event (type=%d) in block %p\n", aEvent.mType, 525 block.get()); 526 } 527 528 result.mInputBlockId = block->GetBlockId(); 529 530 // Note that the |aTarget| the APZCTM sent us may contradict the confirmed 531 // target set on the block. In this case the confirmed target (which may be 532 // null) should take priority. This is equivalent to just always using the 533 // target (confirmed or not) from the block, which is what 534 // ProcessQueue() does. 535 mQueuedInputs.AppendElement(MakeUnique<QueuedInput>(event, *block)); 536 ProcessQueue(); 537 538 return result; 539 } 540 541 APZEventResult InputQueue::ReceivePinchGestureInput( 542 const RefPtr<AsyncPanZoomController>& aTarget, 543 TargetConfirmationFlags aFlags, const PinchGestureInput& aEvent) { 544 APZEventResult result(aTarget, aFlags); 545 546 RefPtr<PinchGestureBlockState> block; 547 if (aEvent.mType != PinchGestureInput::PINCHGESTURE_START) { 548 block = mActivePinchGestureBlock.get(); 549 } 550 551 result.SetStatusAsConsumeDoDefault(aTarget); 552 553 if (!block || block->WasInterrupted()) { 554 if (aEvent.mType != PinchGestureInput::PINCHGESTURE_START) { 555 // Only PINCHGESTURE_START events are allowed to start a new pinch gesture 556 // block. 557 INPQ_LOG("pinchgesture block %p was interrupted %d\n", block.get(), 558 block ? block->WasInterrupted() : 0); 559 return result; 560 } 561 block = new PinchGestureBlockState(aTarget, aFlags); 562 INPQ_LOG("started new pinch gesture block %p id %" PRIu64 563 " for target %p\n", 564 block.get(), block->GetBlockId(), aTarget.get()); 565 566 mActivePinchGestureBlock = block; 567 block->SetNeedsToWaitForContentResponse(true); 568 569 CancelAnimationsForNewBlock(block); 570 MaybeRequestContentResponse(aTarget, block); 571 } else { 572 INPQ_LOG("received new pinch event (type=%d) in block %p\n", aEvent.mType, 573 block.get()); 574 } 575 576 result.mInputBlockId = block->GetBlockId(); 577 578 // Note that the |aTarget| the APZCTM sent us may contradict the confirmed 579 // target set on the block. In this case the confirmed target (which may be 580 // null) should take priority. This is equivalent to just always using the 581 // target (confirmed or not) from the block, which is what 582 // ProcessQueue() does. 583 mQueuedInputs.AppendElement(MakeUnique<QueuedInput>(aEvent, *block)); 584 ProcessQueue(); 585 586 return result; 587 } 588 589 void InputQueue::CancelAnimationsForNewBlock(InputBlockState* aBlock, 590 CancelAnimationFlags aExtraFlags) { 591 // We want to cancel animations here as soon as possible (i.e. without waiting 592 // for content responses) because a finger has gone down and we don't want to 593 // keep moving the content under the finger. However, to prevent "future" 594 // touchstart events from interfering with "past" animations (i.e. from a 595 // previous touch block that is still being processed) we only do this 596 // animation-cancellation if there are no older touch blocks still in the 597 // queue. 598 if (mQueuedInputs.IsEmpty()) { 599 aBlock->GetOverscrollHandoffChain()->CancelAnimations( 600 aExtraFlags | ExcludeOverscroll | ScrollSnap); 601 } 602 } 603 604 bool InputQueue::MaybeRequestContentResponse( 605 const RefPtr<AsyncPanZoomController>& aTarget, 606 CancelableBlockState* aBlock) { 607 bool waitForMainThread = false; 608 if (aBlock->IsTargetConfirmed()) { 609 // Content won't prevent-default this, so we can just set the flag directly. 610 INPQ_LOG("not waiting for content response on block %p\n", aBlock); 611 aBlock->SetContentResponse(false); 612 } else { 613 waitForMainThread = true; 614 } 615 if (aBlock->AsTouchBlock() && 616 !aBlock->AsTouchBlock()->HasAllowedTouchBehaviors()) { 617 INPQ_LOG("waiting for main thread touch-action info on block %p\n", aBlock); 618 waitForMainThread = true; 619 } 620 if (waitForMainThread) { 621 // We either don't know for sure if aTarget is the right APZC, or we may 622 // need to wait to give content the opportunity to prevent-default the 623 // touch events. Either way we schedule a timeout so the main thread stuff 624 // can run. 625 ScheduleMainThreadTimeout(aTarget, aBlock); 626 } 627 return waitForMainThread; 628 } 629 630 uint64_t InputQueue::InjectNewTouchBlock(AsyncPanZoomController* aTarget) { 631 AutoRunImmediateTimeout timeoutRunner{this}; 632 TouchBlockState* block = StartNewTouchBlockForLongTap(aTarget); 633 INPQ_LOG("injecting new touch block %p with id %" PRIu64 " and target %p\n", 634 block, block->GetBlockId(), aTarget); 635 ScheduleMainThreadTimeout(aTarget, block); 636 return block->GetBlockId(); 637 } 638 639 TouchBlockState* InputQueue::StartNewTouchBlock( 640 const RefPtr<AsyncPanZoomController>& aTarget, 641 TargetConfirmationFlags aFlags) { 642 if (mPrevActiveTouchBlock && mActiveTouchBlock && 643 mActiveTouchBlock->ForLongTap()) { 644 mPrevActiveTouchBlock->SetWaitingLongTapResult(false); 645 mPrevActiveTouchBlock = nullptr; 646 } 647 648 TouchBlockState* newBlock = 649 new TouchBlockState(aTarget, aFlags, mTouchCounter); 650 651 mActiveTouchBlock = newBlock; 652 return newBlock; 653 } 654 655 TouchBlockState* InputQueue::StartNewTouchBlockForLongTap( 656 const RefPtr<AsyncPanZoomController>& aTarget) { 657 TouchBlockState* newBlock = new TouchBlockState( 658 aTarget, TargetConfirmationFlags{true}, mTouchCounter); 659 660 TouchBlockState* currentBlock = GetCurrentTouchBlock(); 661 // We should never enter here without a current touch block, because this 662 // codepath is invoked from the OnLongPress handler in 663 // AsyncPanZoomController, which should bail out if there is no current 664 // touch block. 665 MOZ_ASSERT(currentBlock); 666 newBlock->CopyPropertiesFrom(*currentBlock); 667 newBlock->SetForLongTap(); 668 669 // Tell the original touch block that we are going to fire a long tap event. 670 // NOTE: If we get a new touch-move event while we are waiting for a response 671 // of the long-tap event, we need to wait it before processing the original 672 // touch block because if the long-tap event response prevents us from 673 // scrolling we must stop processing any subsequent touch-move events in the 674 // same block. 675 currentBlock->SetWaitingLongTapResult(true); 676 677 // We need to keep the current block alive, it will be used once after this 678 // new touch block for long-tap was processed. 679 mPrevActiveTouchBlock = currentBlock; 680 mActiveTouchBlock = newBlock; 681 return newBlock; 682 } 683 684 InputBlockState* InputQueue::GetCurrentBlock() const { 685 APZThreadUtils::AssertOnControllerThread(); 686 return mQueuedInputs.IsEmpty() ? nullptr : mQueuedInputs[0]->Block(); 687 } 688 689 TouchBlockState* InputQueue::GetCurrentTouchBlock() const { 690 InputBlockState* block = GetCurrentBlock(); 691 return block ? block->AsTouchBlock() : mActiveTouchBlock.get(); 692 } 693 694 WheelBlockState* InputQueue::GetCurrentWheelBlock() const { 695 InputBlockState* block = GetCurrentBlock(); 696 return block ? block->AsWheelBlock() : mActiveWheelBlock.get(); 697 } 698 699 DragBlockState* InputQueue::GetCurrentDragBlock() const { 700 InputBlockState* block = GetCurrentBlock(); 701 return block ? block->AsDragBlock() : mActiveDragBlock.get(); 702 } 703 704 PanGestureBlockState* InputQueue::GetCurrentPanGestureBlock() const { 705 InputBlockState* block = GetCurrentBlock(); 706 return block ? block->AsPanGestureBlock() : mActivePanGestureBlock.get(); 707 } 708 709 PinchGestureBlockState* InputQueue::GetCurrentPinchGestureBlock() const { 710 InputBlockState* block = GetCurrentBlock(); 711 return block ? block->AsPinchGestureBlock() : mActivePinchGestureBlock.get(); 712 } 713 714 KeyboardBlockState* InputQueue::GetCurrentKeyboardBlock() const { 715 InputBlockState* block = GetCurrentBlock(); 716 return block ? block->AsKeyboardBlock() : mActiveKeyboardBlock.get(); 717 } 718 719 WheelBlockState* InputQueue::GetActiveWheelTransaction() const { 720 WheelBlockState* block = mActiveWheelBlock.get(); 721 if (!block || !block->InTransaction()) { 722 return nullptr; 723 } 724 return block; 725 } 726 727 bool InputQueue::HasReadyTouchBlock() const { 728 return !mQueuedInputs.IsEmpty() && 729 mQueuedInputs[0]->Block()->AsTouchBlock() && 730 mQueuedInputs[0]->Block()->AsTouchBlock()->IsReadyForHandling(); 731 } 732 733 bool InputQueue::AllowScrollHandoff() const { 734 if (GetCurrentWheelBlock()) { 735 return GetCurrentWheelBlock()->AllowScrollHandoff(); 736 } 737 if (GetCurrentPanGestureBlock()) { 738 return GetCurrentPanGestureBlock()->AllowScrollHandoff(); 739 } 740 if (GetCurrentKeyboardBlock()) { 741 return GetCurrentKeyboardBlock()->AllowScrollHandoff(); 742 } 743 return true; 744 } 745 746 bool InputQueue::IsDragOnScrollbar(bool aHitScrollbar) { 747 if (!mDragTracker.InDrag()) { 748 return false; 749 } 750 // Now that we know we are in a drag, get the info from the drag tracker. 751 // We keep it in the tracker rather than the block because the block can get 752 // interrupted by something else (like a wheel event) and then a new block 753 // will get created without the info we want. The tracker will persist though. 754 return mDragTracker.IsOnScrollbar(aHitScrollbar); 755 } 756 757 void InputQueue::ScheduleMainThreadTimeout( 758 const RefPtr<AsyncPanZoomController>& aTarget, 759 CancelableBlockState* aBlock) { 760 INPQ_LOG("scheduling main thread timeout for target %p\n", aTarget.get()); 761 RefPtr<Runnable> timeoutTask = NewRunnableMethod<uint64_t>( 762 "layers::InputQueue::MainThreadTimeout", this, 763 &InputQueue::MainThreadTimeout, aBlock->GetBlockId()); 764 int32_t timeout = StaticPrefs::apz_content_response_timeout(); 765 if (timeout == 0) { 766 // If the timeout is zero, treat it as a request to ignore any main 767 // thread confirmation and unconditionally use fallback behaviour for 768 // when a timeout is reached. This codepath is used by tests that 769 // want to exercise the fallback behaviour. 770 // To ensure the fallback behaviour is used unconditionally, the timeout 771 // is run right away instead of using PostDelayedTask(). However, 772 // we can't run it right here, because MainThreadTimeout() expects that 773 // the input block has at least one input event in mQueuedInputs, and 774 // the event that triggered this call may not have been added to 775 // mQueuedInputs yet. 776 mImmediateTimeout = std::move(timeoutTask); 777 } else { 778 aTarget->PostDelayedTask(timeoutTask.forget(), timeout); 779 } 780 } 781 782 InputBlockState* InputQueue::GetBlockForId(uint64_t aInputBlockId) { 783 return FindBlockForId(aInputBlockId, nullptr); 784 } 785 786 void InputQueue::AddInputBlockCallback(uint64_t aInputBlockId, 787 InputBlockCallback&& aCallbackInfo) { 788 mInputBlockCallbacks.insert(InputBlockCallbackMap::value_type( 789 aInputBlockId, std::move(aCallbackInfo))); 790 } 791 792 InputBlockState* InputQueue::FindBlockForId( 793 uint64_t aInputBlockId, InputQueueIterator* aOutFirstInput) { 794 for (auto it = mQueuedInputs.begin(), end = mQueuedInputs.end(); it != end; 795 ++it) { 796 if ((*it)->Block()->GetBlockId() == aInputBlockId) { 797 if (aOutFirstInput) { 798 *aOutFirstInput = InputQueueIterator(it, end); 799 } 800 return (*it)->Block(); 801 } 802 } 803 804 InputBlockState* block = nullptr; 805 if (mActiveTouchBlock && mActiveTouchBlock->GetBlockId() == aInputBlockId) { 806 block = mActiveTouchBlock.get(); 807 } else if (mPrevActiveTouchBlock && 808 mPrevActiveTouchBlock->GetBlockId() == aInputBlockId) { 809 block = mPrevActiveTouchBlock.get(); 810 } else if (mActiveWheelBlock && 811 mActiveWheelBlock->GetBlockId() == aInputBlockId) { 812 block = mActiveWheelBlock.get(); 813 } else if (mActiveDragBlock && 814 mActiveDragBlock->GetBlockId() == aInputBlockId) { 815 block = mActiveDragBlock.get(); 816 } else if (mActivePanGestureBlock && 817 mActivePanGestureBlock->GetBlockId() == aInputBlockId) { 818 block = mActivePanGestureBlock.get(); 819 } else if (mActivePinchGestureBlock && 820 mActivePinchGestureBlock->GetBlockId() == aInputBlockId) { 821 block = mActivePinchGestureBlock.get(); 822 } else if (mActiveKeyboardBlock && 823 mActiveKeyboardBlock->GetBlockId() == aInputBlockId) { 824 block = mActiveKeyboardBlock.get(); 825 } 826 // Since we didn't encounter this block while iterating through mQueuedInputs, 827 // it must have no events associated with it at the moment. 828 if (aOutFirstInput) { 829 *aOutFirstInput = InputQueueIterator(); 830 } 831 return block; 832 } 833 834 void InputQueue::MainThreadTimeout(uint64_t aInputBlockId) { 835 // It's possible that this function gets called after the controller thread 836 // was discarded during shutdown. 837 if (!APZThreadUtils::IsControllerThreadAlive()) { 838 return; 839 } 840 APZThreadUtils::AssertOnControllerThread(); 841 842 INPQ_LOG("got a main thread timeout; block=%" PRIu64 "\n", aInputBlockId); 843 bool success = false; 844 InputQueueIterator firstInput; 845 InputBlockState* inputBlock = FindBlockForId(aInputBlockId, &firstInput); 846 if (inputBlock && inputBlock->AsCancelableBlock()) { 847 CancelableBlockState* block = inputBlock->AsCancelableBlock(); 848 // time out the touch-listener response and also confirm the existing 849 // target apzc in the case where the main thread doesn't get back to us 850 // fast enough. 851 success = block->TimeoutContentResponse(); 852 success |= block->SetConfirmedTargetApzc( 853 block->GetTargetApzc(), 854 InputBlockState::TargetConfirmationState::eTimedOut, firstInput, 855 // This actually could be a scrollbar drag, but we pass 856 // aForScrollbarDrag=false because for scrollbar drags, 857 // SetConfirmedTargetApzc() will also be called by ConfirmDragBlock(), 858 // and we pass aForScrollbarDrag=true there. 859 false); 860 } else if (inputBlock) { 861 NS_WARNING("input block is not a cancelable block"); 862 } 863 if (success) { 864 if (inputBlock->AsTouchBlock() && inputBlock->AsTouchBlock()->IsInSlop()) { 865 // If the touch block is still in slop, it's still possible this block 866 // needs to send a touchmove to content after the long-press gesture 867 // since preventDefault() in a touchmove event handler should stop 868 // handling the block at all. 869 inputBlock->AsTouchBlock()->SetNeedsToWaitTouchMove(true); 870 } 871 ProcessQueue(); 872 } 873 } 874 875 void InputQueue::MaybeLongTapTimeout(uint64_t aInputBlockId) { 876 // It's possible that this function gets called after the controller thread 877 // was discarded during shutdown. 878 if (!APZThreadUtils::IsControllerThreadAlive()) { 879 return; 880 } 881 APZThreadUtils::AssertOnControllerThread(); 882 883 INPQ_LOG("got a maybe-long-tap timeout; block=%" PRIu64 "\n", aInputBlockId); 884 885 InputBlockState* inputBlock = FindBlockForId(aInputBlockId, nullptr); 886 MOZ_ASSERT(!inputBlock || inputBlock->AsTouchBlock()); 887 if (inputBlock && inputBlock->AsTouchBlock()->IsInSlop()) { 888 // If the block is still in slop, it won't have sent a touchmove to content 889 // and so content will not have sent a content response. But also it means 890 // the touchstart should trigger a long-press gesture so let's force the 891 // block to get processed now. 892 MainThreadTimeout(aInputBlockId); 893 } 894 } 895 896 void InputQueue::ContentReceivedInputBlock(uint64_t aInputBlockId, 897 bool aPreventDefault) { 898 APZThreadUtils::AssertOnControllerThread(); 899 900 INPQ_LOG("got a content response; block=%" PRIu64 " preventDefault=%d\n", 901 aInputBlockId, aPreventDefault); 902 bool success = false; 903 InputBlockState* inputBlock = FindBlockForId(aInputBlockId, nullptr); 904 if (inputBlock && inputBlock->AsCancelableBlock()) { 905 CancelableBlockState* block = inputBlock->AsCancelableBlock(); 906 success = block->SetContentResponse(aPreventDefault); 907 } else if (inputBlock) { 908 NS_WARNING("input block is not a cancelable block"); 909 } else { 910 INPQ_LOG("couldn't find block=%" PRIu64 "\n", aInputBlockId); 911 } 912 if (success) { 913 if (ProcessQueue()) { 914 // If we've switched the active touch block back to the original touch 915 // block from the block for long-tap, run ProcessQueue again. 916 // If we haven't yet received new touch-move events which need to be 917 // processed (e.g. we are waiting for a content response for a touch-move 918 // event), below ProcessQueue call is mostly no-op. 919 ProcessQueue(); 920 } 921 } 922 } 923 924 void InputQueue::SetConfirmedTargetApzc( 925 uint64_t aInputBlockId, const RefPtr<AsyncPanZoomController>& aTargetApzc) { 926 APZThreadUtils::AssertOnControllerThread(); 927 928 INPQ_LOG("got a target apzc; block=%" PRIu64 " guid=%s\n", aInputBlockId, 929 aTargetApzc ? ToString(aTargetApzc->GetGuid()).c_str() : ""); 930 bool success = false; 931 InputQueueIterator firstInput; 932 InputBlockState* inputBlock = FindBlockForId(aInputBlockId, &firstInput); 933 if (inputBlock && inputBlock->AsCancelableBlock()) { 934 CancelableBlockState* block = inputBlock->AsCancelableBlock(); 935 success = block->SetConfirmedTargetApzc( 936 aTargetApzc, InputBlockState::TargetConfirmationState::eConfirmed, 937 firstInput, 938 // This actually could be a scrollbar drag, but we pass 939 // aForScrollbarDrag=false because for scrollbar drags, 940 // SetConfirmedTargetApzc() will also be called by ConfirmDragBlock(), 941 // and we pass aForScrollbarDrag=true there. 942 false); 943 } else if (inputBlock) { 944 NS_WARNING("input block is not a cancelable block"); 945 } 946 if (success) { 947 ProcessQueue(); 948 } 949 } 950 951 void InputQueue::ConfirmDragBlock( 952 uint64_t aInputBlockId, const RefPtr<AsyncPanZoomController>& aTargetApzc, 953 const AsyncDragMetrics& aDragMetrics) { 954 APZThreadUtils::AssertOnControllerThread(); 955 956 INPQ_LOG("got a target apzc; block=%" PRIu64 " guid=%s dragtarget=%" PRIu64 957 "\n", 958 aInputBlockId, 959 aTargetApzc ? ToString(aTargetApzc->GetGuid()).c_str() : "", 960 aDragMetrics.mViewId); 961 bool success = false; 962 InputQueueIterator firstInput; 963 InputBlockState* inputBlock = FindBlockForId(aInputBlockId, &firstInput); 964 if (inputBlock && inputBlock->AsDragBlock()) { 965 DragBlockState* block = inputBlock->AsDragBlock(); 966 967 // We use the target initial scrollable rect for updating the thumb position 968 // during dragging the thumb even if the scrollable rect got expanded during 969 // the drag. 970 block->SetDragMetrics(aDragMetrics, aTargetApzc->GetScrollableRect()); 971 success = block->SetConfirmedTargetApzc( 972 aTargetApzc, InputBlockState::TargetConfirmationState::eConfirmed, 973 firstInput, 974 /* aForScrollbarDrag = */ true); 975 } 976 if (success) { 977 ProcessQueue(); 978 } 979 } 980 981 void InputQueue::SetAllowedTouchBehavior( 982 uint64_t aInputBlockId, const nsTArray<TouchBehaviorFlags>& aBehaviors) { 983 APZThreadUtils::AssertOnControllerThread(); 984 985 INPQ_LOG("got allowed touch behaviours; block=%" PRIu64 "\n", aInputBlockId); 986 bool success = false; 987 InputBlockState* inputBlock = FindBlockForId(aInputBlockId, nullptr); 988 if (inputBlock && inputBlock->AsTouchBlock()) { 989 TouchBlockState* block = inputBlock->AsTouchBlock(); 990 success = block->SetAllowedTouchBehaviors(aBehaviors); 991 } else if (inputBlock) { 992 NS_WARNING("input block is not a touch block"); 993 } 994 if (success) { 995 ProcessQueue(); 996 } 997 } 998 999 void InputQueue::SetBrowserGestureResponse(uint64_t aInputBlockId, 1000 BrowserGestureResponse aResponse) { 1001 InputBlockState* inputBlock = FindBlockForId(aInputBlockId, nullptr); 1002 1003 if (inputBlock && inputBlock->AsPanGestureBlock()) { 1004 PanGestureBlockState* block = inputBlock->AsPanGestureBlock(); 1005 block->SetBrowserGestureResponse(aResponse); 1006 } else if (inputBlock) { 1007 NS_WARNING("input block is not a pan gesture block"); 1008 } 1009 ProcessQueue(); 1010 } 1011 1012 static APZHandledResult GetHandledResultFor( 1013 const AsyncPanZoomController* aApzc, 1014 const InputBlockState* aCurrentInputBlock, const InputData& aEvent) { 1015 if (aCurrentInputBlock->ShouldDropEvents()) { 1016 return APZHandledResult{APZHandledPlace::HandledByContent, aApzc}; 1017 } 1018 1019 // For the remainder of the function, we know the event was *not* 1020 // preventDefault()-ed, so we can pass DispatchToContent::No to helpers. 1021 1022 if (!aApzc) { 1023 return APZHandledResult{APZHandledPlace::HandledByContent, aApzc}; 1024 } 1025 1026 Maybe<APZHandledResult> result = 1027 APZHandledResult::Initialize(aApzc, DispatchToContent::No); 1028 1029 if (aEvent.mInputType == MULTITOUCH_INPUT) { 1030 PointerEventsConsumableFlags consumableFlags = 1031 aApzc->ArePointerEventsConsumable(aCurrentInputBlock->AsTouchBlock(), 1032 aEvent.AsMultiTouchInput()); 1033 APZHandledResult::UpdateForTouchEvent(result, *aCurrentInputBlock, 1034 consumableFlags, aApzc, 1035 DispatchToContent::No); 1036 } 1037 // Initialize() and UpdateForTouchEvent() can only produce Nothing() in 1038 // case of aDispatchToContent=true. 1039 MOZ_RELEASE_ASSERT(result.isSome()); 1040 return *result; 1041 } 1042 1043 bool InputQueue::ProcessQueue() { 1044 APZThreadUtils::AssertOnControllerThread(); 1045 1046 while (!mQueuedInputs.IsEmpty()) { 1047 InputBlockState* curBlock = mQueuedInputs[0]->Block(); 1048 CancelableBlockState* cancelable = curBlock->AsCancelableBlock(); 1049 if (cancelable && !cancelable->IsReadyForHandling()) { 1050 if (MOZ_UNLIKELY(INPQ_LOG_TEST())) { 1051 nsAutoCString additionalLog; 1052 if (curBlock->AsTouchBlock()) { 1053 // touch 1054 additionalLog.AppendPrintf( 1055 "waiting-long-tap-result: %d allowed-touch-behaviors: %d", 1056 curBlock->AsTouchBlock()->IsWaitingLongTapResult(), 1057 curBlock->AsTouchBlock()->HasAllowedTouchBehaviors()); 1058 } else if (curBlock->AsPanGestureBlock()) { 1059 // pan gesture 1060 additionalLog.AppendPrintf( 1061 "waiting-browser-gesture-response: %d waiting-content-response: " 1062 "%d", 1063 curBlock->AsPanGestureBlock() 1064 ->IsWaitingForBrowserGestureResponse(), 1065 curBlock->AsPanGestureBlock()->IsWaitingForContentResponse()); 1066 } else if (curBlock->AsPinchGestureBlock()) { 1067 // pinch gesture 1068 additionalLog.AppendPrintf( 1069 "waiting-content-response: %d", 1070 curBlock->AsPinchGestureBlock()->IsWaitingForContentResponse()); 1071 } 1072 1073 INPQ_LOG( 1074 "skip processing %s block %p; target-confirmed: %d " 1075 "content-responded: %d content-response-expired: %d %s", 1076 cancelable->Type(), cancelable, cancelable->IsTargetConfirmed(), 1077 cancelable->HasContentResponded(), 1078 cancelable->IsContentResponseTimerExpired(), additionalLog.get()); 1079 } 1080 break; 1081 } 1082 1083 INPQ_LOG( 1084 "processing input from block %p; preventDefault %d shouldDropEvents %d " 1085 "target %p\n", 1086 curBlock, cancelable && cancelable->IsDefaultPrevented(), 1087 curBlock->ShouldDropEvents(), curBlock->GetTargetApzc().get()); 1088 RefPtr<AsyncPanZoomController> target = curBlock->GetTargetApzc(); 1089 1090 // If there is an input block callback registered for this 1091 // input block, invoke it. 1092 // 1093 // NOTE: In the case where the block is a touch block and the block is not 1094 // ready to invoke the callback because of waiting a touch move response 1095 // from content, we skip the block. 1096 if (!curBlock->AsTouchBlock() || 1097 curBlock->AsTouchBlock()->IsReadyForCallback()) { 1098 auto it = mInputBlockCallbacks.find(curBlock->GetBlockId()); 1099 if (it != mInputBlockCallbacks.end()) { 1100 INPQ_LOG("invoking the callback for input from block %p id %" PRIu64 1101 "\n", 1102 curBlock, curBlock->GetBlockId()); 1103 APZHandledResult handledResult = 1104 GetHandledResultFor(target, curBlock, *(mQueuedInputs[0]->Input())); 1105 it->second(curBlock->GetBlockId(), handledResult); 1106 // The callback is one-shot; discard it after calling it. 1107 mInputBlockCallbacks.erase(it); 1108 } 1109 } 1110 1111 // target may be null here if the initial target was unconfirmed and then 1112 // we later got a confirmed null target. in that case drop the events. 1113 if (target) { 1114 // If the event is targeting a different APZC than the previous one, 1115 // we want to clear the previous APZC's gesture state regardless of 1116 // whether we're actually dispatching the event or not. 1117 if (mLastActiveApzc && mLastActiveApzc != target && 1118 mTouchCounter.GetActiveTouchCount() > 0) { 1119 mLastActiveApzc->ResetTouchInputState(); 1120 } 1121 if (curBlock->ShouldDropEvents()) { 1122 if (curBlock->AsTouchBlock()) { 1123 target->ResetTouchInputState(); 1124 } else if (curBlock->AsPanGestureBlock()) { 1125 target->ResetPanGestureInputState(); 1126 } 1127 } else { 1128 UpdateActiveApzc(target); 1129 curBlock->DispatchEvent(*(mQueuedInputs[0]->Input())); 1130 } 1131 } 1132 mQueuedInputs.RemoveElementAt(0); 1133 } 1134 1135 bool processQueueAgain = false; 1136 if (CanDiscardBlock(mActiveTouchBlock)) { 1137 const bool forLongTap = mActiveTouchBlock->ForLongTap(); 1138 const bool wasDefaultPrevented = mActiveTouchBlock->IsDefaultPrevented(); 1139 INPQ_LOG("discarding a touch block %p id %" PRIu64 "\n", 1140 mActiveTouchBlock.get(), mActiveTouchBlock->GetBlockId()); 1141 mActiveTouchBlock = nullptr; 1142 MOZ_ASSERT_IF(forLongTap, mPrevActiveTouchBlock); 1143 if (forLongTap) { 1144 INPQ_LOG("switching back to the original touch block %p id %" PRIu64 "\n", 1145 mPrevActiveTouchBlock.get(), 1146 mPrevActiveTouchBlock->GetBlockId()); 1147 1148 mPrevActiveTouchBlock->SetLongTapProcessed(); 1149 if (wasDefaultPrevented && !mPrevActiveTouchBlock->IsDefaultPrevented()) { 1150 // Take over the preventDefaulted info for the long-tap event (i.e. for 1151 // the contextmenu event) to the original touch block so that the 1152 // original touch block will never process incoming touch events. 1153 mPrevActiveTouchBlock->ResetContentResponseTimerExpired(); 1154 mPrevActiveTouchBlock->SetContentResponse(true); 1155 } 1156 mActiveTouchBlock = mPrevActiveTouchBlock; 1157 mPrevActiveTouchBlock = nullptr; 1158 processQueueAgain = true; 1159 } 1160 } 1161 if (CanDiscardBlock(mActiveWheelBlock)) { 1162 mActiveWheelBlock = nullptr; 1163 } 1164 if (CanDiscardBlock(mActiveDragBlock)) { 1165 mActiveDragBlock = nullptr; 1166 } 1167 if (CanDiscardBlock(mActivePanGestureBlock)) { 1168 mActivePanGestureBlock = nullptr; 1169 } 1170 if (CanDiscardBlock(mActivePinchGestureBlock)) { 1171 mActivePinchGestureBlock = nullptr; 1172 } 1173 if (CanDiscardBlock(mActiveKeyboardBlock)) { 1174 mActiveKeyboardBlock = nullptr; 1175 } 1176 1177 return processQueueAgain; 1178 } 1179 1180 bool InputQueue::CanDiscardBlock(InputBlockState* aBlock) { 1181 if (!aBlock || 1182 (aBlock->AsCancelableBlock() && 1183 !aBlock->AsCancelableBlock()->IsReadyForHandling()) || 1184 aBlock->MustStayActive()) { 1185 return false; 1186 } 1187 InputQueueIterator firstInput; 1188 FindBlockForId(aBlock->GetBlockId(), &firstInput); 1189 if (firstInput) { 1190 // The block has at least one input event still in the queue, so it's 1191 // not depleted 1192 return false; 1193 } 1194 return true; 1195 } 1196 1197 void InputQueue::UpdateActiveApzc( 1198 const RefPtr<AsyncPanZoomController>& aNewActive) { 1199 mLastActiveApzc = aNewActive; 1200 } 1201 1202 void InputQueue::Clear() { 1203 // On Android, where the controller thread is the Android UI thread, 1204 // it's possible for this to be called after the main thread has 1205 // already run the shutdown task that clears the state used to 1206 // implement APZThreadUtils::AssertOnControllerThread(). 1207 // In such cases, we still want to perform the cleanup. 1208 if (APZThreadUtils::IsControllerThreadAlive()) { 1209 APZThreadUtils::AssertOnControllerThread(); 1210 } 1211 1212 mQueuedInputs.Clear(); 1213 mActiveTouchBlock = nullptr; 1214 mPrevActiveTouchBlock = nullptr; 1215 mActiveWheelBlock = nullptr; 1216 mActiveDragBlock = nullptr; 1217 mActivePanGestureBlock = nullptr; 1218 mActivePinchGestureBlock = nullptr; 1219 mActiveKeyboardBlock = nullptr; 1220 mLastActiveApzc = nullptr; 1221 } 1222 1223 InputQueue::AutoRunImmediateTimeout::AutoRunImmediateTimeout(InputQueue* aQueue) 1224 : mQueue(aQueue) { 1225 MOZ_ASSERT(!mQueue->mImmediateTimeout); 1226 } 1227 1228 InputQueue::AutoRunImmediateTimeout::~AutoRunImmediateTimeout() { 1229 if (mQueue->mImmediateTimeout) { 1230 mQueue->mImmediateTimeout->Run(); 1231 mQueue->mImmediateTimeout = nullptr; 1232 } 1233 } 1234 1235 } // namespace layers 1236 } // namespace mozilla