InputBlockState.cpp (30764B)
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 "InputBlockState.h" 8 9 #include "APZUtils.h" 10 #include "AsyncPanZoomController.h" // for AsyncPanZoomController 11 12 #include "mozilla/MouseEvents.h" 13 #include "mozilla/StaticPrefs_apz.h" 14 #include "mozilla/StaticPrefs_browser.h" 15 #include "mozilla/StaticPrefs_layout.h" 16 #include "mozilla/StaticPrefs_mousewheel.h" 17 #include "mozilla/StaticPrefs_test.h" 18 #include "mozilla/ToString.h" 19 #include "mozilla/layers/APZEventState.h" 20 #include "mozilla/layers/IAPZCTreeManager.h" // for AllowedTouchBehavior 21 #include "OverscrollHandoffState.h" 22 #include "QueuedInput.h" 23 24 static mozilla::LazyLogModule sApzIbsLog("apz.inputstate"); 25 #define TBS_LOG(...) MOZ_LOG(sApzIbsLog, LogLevel::Debug, (__VA_ARGS__)) 26 27 namespace mozilla { 28 namespace layers { 29 30 static uint64_t sBlockCounter = InputBlockState::NO_BLOCK_ID + 1; 31 32 InputBlockState::InputBlockState( 33 const RefPtr<AsyncPanZoomController>& aTargetApzc, 34 TargetConfirmationFlags aFlags) 35 : mTargetApzc(aTargetApzc), 36 mRequiresTargetConfirmation(aFlags.mRequiresTargetConfirmation), 37 mBlockId(sBlockCounter++), 38 mTransformToApzc(aTargetApzc->GetTransformToThis()) { 39 // We should never be constructed with a nullptr target. 40 MOZ_ASSERT(mTargetApzc); 41 mOverscrollHandoffChain = mTargetApzc->BuildOverscrollHandoffChain(); 42 // If a new block starts on a scrollthumb and we have APZ scrollbar 43 // dragging enabled, defer confirmation until we get the drag metrics 44 // for the thumb. 45 bool startingDrag = StaticPrefs::apz_drag_enabled() && aFlags.mHitScrollThumb; 46 mTargetConfirmed = aFlags.mTargetConfirmed && !startingDrag 47 ? TargetConfirmationState::eConfirmed 48 : TargetConfirmationState::eUnconfirmed; 49 } 50 51 bool InputBlockState::SetConfirmedTargetApzc( 52 const RefPtr<AsyncPanZoomController>& aTargetApzc, 53 TargetConfirmationState aState, InputQueueIterator aFirstInput, 54 bool aForScrollbarDrag) { 55 MOZ_ASSERT(aState == TargetConfirmationState::eConfirmed || 56 aState == TargetConfirmationState::eTimedOut); 57 58 // Sometimes, bugs in compositor hit testing can lead to APZ confirming 59 // a different target than the main thread. If this happens for a drag 60 // block created for a scrollbar drag, the consequences can be fairly 61 // user-unfriendly, such as the scrollbar not being draggable at all, 62 // or it scrolling the contents of the wrong scrollframe. In debug 63 // builds, we assert in this situation, so that the 64 // underlying compositor hit testing bug can be fixed. In release builds, 65 // however, we just silently accept the main thread's confirmed target, 66 // which will produce the expected behaviour (apart from drag events 67 // received so far being dropped). 68 if (AsDragBlock() && aForScrollbarDrag && 69 mTargetConfirmed == TargetConfirmationState::eConfirmed && 70 aState == TargetConfirmationState::eConfirmed && mTargetApzc && 71 aTargetApzc && mTargetApzc->GetGuid() != aTargetApzc->GetGuid()) { 72 MOZ_ASSERT(false, 73 "APZ and main thread confirmed scrollbar drag block with " 74 "different targets"); 75 UpdateTargetApzc(aTargetApzc); 76 return true; 77 } 78 79 if (mTargetConfirmed != TargetConfirmationState::eUnconfirmed) { 80 return false; 81 } 82 mTargetConfirmed = aState; 83 84 TBS_LOG("%p got confirmed target APZC %p\n", this, mTargetApzc.get()); 85 if (mTargetApzc == aTargetApzc) { 86 // The confirmed target is the same as the tentative one, so we're done. 87 return true; 88 } 89 90 TBS_LOG("%p replacing unconfirmed target %p with real target %p\n", this, 91 mTargetApzc.get(), aTargetApzc.get()); 92 93 UpdateTargetApzc(aTargetApzc); 94 return true; 95 } 96 97 void InputBlockState::UpdateTargetApzc( 98 const RefPtr<AsyncPanZoomController>& aTargetApzc) { 99 if (mTargetApzc == aTargetApzc) { 100 MOZ_ASSERT_UNREACHABLE( 101 "The new target APZC should be different from the old one"); 102 return; 103 } 104 105 if (mTargetApzc) { 106 // Restore overscroll state on the previous target APZC and ancestor APZCs 107 // in the scroll handoff chain other than the new one. 108 mTargetApzc->SnapBackIfOverscrolled(); 109 110 uint32_t i = mOverscrollHandoffChain->IndexOf(mTargetApzc) + 1; 111 for (; i < mOverscrollHandoffChain->Length(); i++) { 112 AsyncPanZoomController* apzc = mOverscrollHandoffChain->GetApzcAtIndex(i); 113 if (apzc != aTargetApzc) { 114 MOZ_ASSERT(!apzc->IsOverscrolled() || 115 apzc->IsOverscrollAnimationRunning()); 116 apzc->SnapBackIfOverscrolled(); 117 } 118 } 119 } 120 121 // note that aTargetApzc MAY be null here. 122 mTargetApzc = aTargetApzc; 123 mTransformToApzc = aTargetApzc ? aTargetApzc->GetTransformToThis() 124 : ScreenToParentLayerMatrix4x4(); 125 mOverscrollHandoffChain = 126 (mTargetApzc ? mTargetApzc->BuildOverscrollHandoffChain() : nullptr); 127 } 128 129 const RefPtr<AsyncPanZoomController>& InputBlockState::GetTargetApzc() const { 130 return mTargetApzc; 131 } 132 133 const RefPtr<const OverscrollHandoffChain>& 134 InputBlockState::GetOverscrollHandoffChain() const { 135 return mOverscrollHandoffChain; 136 } 137 138 uint64_t InputBlockState::GetBlockId() const { return mBlockId; } 139 140 bool InputBlockState::IsTargetConfirmed() const { 141 return mTargetConfirmed != TargetConfirmationState::eUnconfirmed; 142 } 143 144 bool InputBlockState::ShouldDropEvents() const { 145 return mRequiresTargetConfirmation && 146 (mTargetConfirmed != TargetConfirmationState::eConfirmed); 147 } 148 149 bool InputBlockState::IsDownchainOf(AsyncPanZoomController* aA, 150 AsyncPanZoomController* aB) const { 151 if (aA == aB) { 152 return true; 153 } 154 155 bool seenA = false; 156 for (size_t i = 0; i < mOverscrollHandoffChain->Length(); ++i) { 157 AsyncPanZoomController* apzc = mOverscrollHandoffChain->GetApzcAtIndex(i); 158 if (apzc == aB) { 159 return seenA; 160 } 161 if (apzc == aA) { 162 seenA = true; 163 } 164 } 165 return false; 166 } 167 168 void InputBlockState::SetScrolledApzc(AsyncPanZoomController* aApzc) { 169 // An input block should only have one scrolled APZC. 170 MOZ_ASSERT(!mScrolledApzc || (StaticPrefs::apz_allow_immediate_handoff() 171 ? IsDownchainOf(mScrolledApzc, aApzc) 172 : mScrolledApzc == aApzc)); 173 174 mScrolledApzc = aApzc; 175 } 176 177 AsyncPanZoomController* InputBlockState::GetScrolledApzc() const { 178 return mScrolledApzc; 179 } 180 181 bool InputBlockState::IsDownchainOfScrolledApzc( 182 AsyncPanZoomController* aApzc) const { 183 MOZ_ASSERT(aApzc && mScrolledApzc); 184 185 return IsDownchainOf(mScrolledApzc, aApzc); 186 } 187 188 void InputBlockState::DispatchEvent(const InputData& aEvent) const { 189 GetTargetApzc()->HandleInputEvent(aEvent, mTransformToApzc); 190 } 191 192 CancelableBlockState::CancelableBlockState( 193 const RefPtr<AsyncPanZoomController>& aTargetApzc, 194 TargetConfirmationFlags aFlags) 195 : InputBlockState(aTargetApzc, aFlags), 196 mPreventDefault(false), 197 mContentResponded(false), 198 mContentResponseTimerExpired(false), 199 mHasStateBeenReset(false) {} 200 201 bool CancelableBlockState::SetContentResponse(bool aPreventDefault) { 202 if (mContentResponded) { 203 return false; 204 } 205 TBS_LOG("%p got content response %d with timer expired %d\n", this, 206 aPreventDefault, mContentResponseTimerExpired); 207 mPreventDefault = aPreventDefault; 208 mContentResponded = true; 209 return true; 210 } 211 212 bool CancelableBlockState::TimeoutContentResponse() { 213 if (mContentResponseTimerExpired) { 214 return false; 215 } 216 TBS_LOG("%p got content timer expired with response received %d\n", this, 217 mContentResponded); 218 if (!mContentResponded) { 219 mPreventDefault = false; 220 } 221 mContentResponseTimerExpired = true; 222 return true; 223 } 224 225 bool CancelableBlockState::IsContentResponseTimerExpired() const { 226 return mContentResponseTimerExpired; 227 } 228 229 bool CancelableBlockState::IsDefaultPrevented() const { 230 MOZ_ASSERT(mContentResponded || mContentResponseTimerExpired); 231 return mPreventDefault; 232 } 233 234 bool CancelableBlockState::IsReadyForHandling() const { 235 if (!IsTargetConfirmed()) { 236 return false; 237 } 238 return mContentResponded || mContentResponseTimerExpired; 239 } 240 241 bool CancelableBlockState::ShouldDropEvents() const { 242 return InputBlockState::ShouldDropEvents() || IsDefaultPrevented(); 243 } 244 245 DragBlockState::DragBlockState( 246 const RefPtr<AsyncPanZoomController>& aTargetApzc, 247 TargetConfirmationFlags aFlags, const MouseInput& aInitialEvent) 248 : CancelableBlockState(aTargetApzc, aFlags), mReceivedMouseUp(false) {} 249 250 bool DragBlockState::HasReceivedMouseUp() { return mReceivedMouseUp; } 251 252 void DragBlockState::MarkMouseUpReceived() { mReceivedMouseUp = true; } 253 254 void DragBlockState::SetInitialThumbPos(OuterCSSCoord aThumbPos) { 255 mInitialThumbPos = aThumbPos; 256 } 257 258 void DragBlockState::SetDragMetrics(const AsyncDragMetrics& aDragMetrics, 259 const CSSRect& aScrollableRect) { 260 mDragMetrics = aDragMetrics; 261 mInitialScrollableRect = aScrollableRect; 262 } 263 264 void DragBlockState::DispatchEvent(const InputData& aEvent) const { 265 MouseInput mouseInput = aEvent.AsMouseInput(); 266 if (!mouseInput.TransformToLocal(mTransformToApzc)) { 267 return; 268 } 269 270 GetTargetApzc()->HandleDragEvent(mouseInput, mDragMetrics, mInitialThumbPos, 271 mInitialScrollableRect); 272 } 273 274 bool DragBlockState::MustStayActive() { return !mReceivedMouseUp; } 275 276 const char* DragBlockState::Type() { return "drag"; } 277 // This is used to track the current wheel transaction. 278 static uint64_t sLastWheelBlockId = InputBlockState::NO_BLOCK_ID; 279 280 WheelBlockState::WheelBlockState( 281 const RefPtr<AsyncPanZoomController>& aTargetApzc, 282 TargetConfirmationFlags aFlags, const ScrollWheelInput& aInitialEvent) 283 : CancelableBlockState(aTargetApzc, aFlags), 284 mScrollSeriesCounter(0), 285 mTransactionEnded(false) { 286 sLastWheelBlockId = GetBlockId(); 287 288 if (aFlags.mTargetConfirmed) { 289 // Find the nearest APZC in the overscroll handoff chain that is scrollable. 290 // If we get a content confirmation later that the apzc is different, then 291 // content should have found a scrollable apzc, so we don't need to handle 292 // that case. 293 RefPtr<AsyncPanZoomController> apzc = 294 mOverscrollHandoffChain->FindFirstScrollable(aInitialEvent, 295 &mAllowedScrollDirections); 296 297 if (apzc) { 298 if (apzc != GetTargetApzc()) { 299 UpdateTargetApzc(apzc); 300 } 301 } else if (!mOverscrollHandoffChain->CanBePanned( 302 mOverscrollHandoffChain->GetApzcAtIndex(0))) { 303 // If there's absolutely nothing scrollable start a transaction and mark 304 // this as such to we know to store our EventTime. 305 mIsScrollable = false; 306 } else { 307 // Scrollable, but not in this direction. 308 EndTransaction(); 309 } 310 } 311 } 312 313 bool WheelBlockState::SetContentResponse(bool aPreventDefault) { 314 if (aPreventDefault) { 315 EndTransaction(); 316 } 317 return CancelableBlockState::SetContentResponse(aPreventDefault); 318 } 319 320 bool WheelBlockState::SetConfirmedTargetApzc( 321 const RefPtr<AsyncPanZoomController>& aTargetApzc, 322 TargetConfirmationState aState, InputQueueIterator aFirstInput, 323 bool aForScrollbarDrag) { 324 // The APZC that we find via APZCCallbackHelpers may not be the same APZC 325 // ESM or OverscrollHandoff would have computed. Make sure we get the right 326 // one by looking for the first apzc the next pending event can scroll. 327 RefPtr<AsyncPanZoomController> apzc = aTargetApzc; 328 if (apzc && aFirstInput) { 329 auto handoffChain = apzc->BuildOverscrollHandoffChain(); 330 apzc = handoffChain->FindFirstScrollable(*aFirstInput->Input(), 331 &mAllowedScrollDirections); 332 333 // If the first event in the input block cannot scroll any APZC, 334 // iterate through the input queue and try subsequent events in the block. 335 // This avoids dropping an entire block where some events could have caused 336 // scrolling. 337 while (!apzc) { 338 ++aFirstInput; 339 if (!aFirstInput) break; 340 if (aFirstInput->Block() != this) { 341 continue; 342 } 343 apzc = handoffChain->FindFirstScrollable(*aFirstInput->Input(), 344 &mAllowedScrollDirections); 345 } 346 } 347 348 InputBlockState::SetConfirmedTargetApzc(apzc, aState, aFirstInput, 349 aForScrollbarDrag); 350 return true; 351 } 352 353 void WheelBlockState::Update(ScrollWheelInput& aEvent) { 354 // We might not be in a transaction if the block never started in a 355 // transaction - for example, if nothing was scrollable. 356 if (!InTransaction()) { 357 return; 358 } 359 360 // The current "scroll series" is a like a sub-transaction. It has a separate 361 // timeout of 80ms. Since we need to compute wheel deltas at different phases 362 // of a transaction (for example, when it is updated, and later when the 363 // event action is taken), we affix the scroll series counter to the event. 364 // This makes GetScrollWheelDelta() consistent. 365 if (!mLastEventTime.IsNull() && 366 (aEvent.mTimeStamp - mLastEventTime).ToMilliseconds() > 367 StaticPrefs::mousewheel_scroll_series_timeout()) { 368 mScrollSeriesCounter = 0; 369 } 370 aEvent.mScrollSeriesNumber = ++mScrollSeriesCounter; 371 372 // If we can't scroll in the direction of the wheel event, we don't update 373 // the last move time. This allows us to timeout a transaction even if the 374 // mouse isn't moving. 375 // 376 // We skip this check if the target is not yet confirmed, so that when it is 377 // confirmed, we don't timeout the transaction. 378 RefPtr<AsyncPanZoomController> apzc = GetTargetApzc(); 379 if (mIsScrollable && IsTargetConfirmed() && !apzc->CanScroll(aEvent)) { 380 return; 381 } 382 383 // Update the time of the last known good event, and reset the mouse move 384 // time to null. This will reset the delays on both the general transaction 385 // timeout and the mouse-move-in-frame timeout. 386 mLastEventTime = aEvent.mTimeStamp; 387 mLastMouseMove = TimeStamp(); 388 } 389 390 Maybe<LayersId> WheelBlockState::WheelTransactionLayersId() const { 391 return (InTransaction() && TargetApzc()) ? Some(TargetApzc()->GetLayersId()) 392 : Nothing(); 393 } 394 395 bool WheelBlockState::MustStayActive() { return !mTransactionEnded; } 396 397 const char* WheelBlockState::Type() { return "scroll wheel"; } 398 399 bool WheelBlockState::ShouldAcceptNewEvent() const { 400 if (!InTransaction()) { 401 // If we're not in a transaction, start a new one. 402 return false; 403 } 404 405 RefPtr<AsyncPanZoomController> apzc = GetTargetApzc(); 406 if (apzc->IsDestroyed()) { 407 return false; 408 } 409 410 return true; 411 } 412 413 bool WheelBlockState::MaybeTimeout(const ScrollWheelInput& aEvent) { 414 MOZ_ASSERT(InTransaction()); 415 416 if (MaybeTimeout(aEvent.mTimeStamp)) { 417 return true; 418 } 419 420 if (!mLastMouseMove.IsNull()) { 421 // If there's a recent mouse movement, we can time out the transaction 422 // early. 423 TimeDuration duration = TimeStamp::Now() - mLastMouseMove; 424 if (duration.ToMilliseconds() >= 425 StaticPrefs::mousewheel_transaction_ignoremovedelay()) { 426 TBS_LOG("%p wheel transaction timed out after mouse move\n", this); 427 EndTransaction(); 428 return true; 429 } 430 } 431 432 return false; 433 } 434 435 bool WheelBlockState::MaybeTimeout(const TimeStamp& aTimeStamp) { 436 MOZ_ASSERT(InTransaction()); 437 438 // End the transaction if the event occurred > 1.5s after the most recently 439 // seen wheel event. 440 TimeDuration duration = aTimeStamp - mLastEventTime; 441 if (duration.ToMilliseconds() < 442 StaticPrefs::mousewheel_transaction_timeout()) { 443 return false; 444 } 445 446 TBS_LOG("%p wheel transaction timed out\n", this); 447 448 if (StaticPrefs::test_mousescroll()) { 449 RefPtr<AsyncPanZoomController> apzc = GetTargetApzc(); 450 apzc->NotifyMozMouseScrollEvent(u"MozMouseScrollTransactionTimeout"_ns); 451 } 452 453 EndTransaction(); 454 return true; 455 } 456 457 void WheelBlockState::OnMouseMove( 458 const ScreenIntPoint& aPoint, 459 const Maybe<ScrollableLayerGuid>& aTargetGuid) { 460 MOZ_ASSERT(InTransaction()); 461 462 if (!GetTargetApzc()->Contains(aPoint) || 463 // If the mouse moved over to a different APZC, `mIsScrollable` 464 // may no longer be false and needs to be recomputed. 465 (!mIsScrollable && aTargetGuid.isSome() && 466 aTargetGuid.value() != GetTargetApzc()->GetGuid())) { 467 EndTransaction(); 468 return; 469 } 470 471 if (mLastMouseMove.IsNull()) { 472 // If the cursor is moving inside the frame, and it is more than the 473 // ignoremovedelay time since the last scroll operation, we record 474 // this as the most recent mouse movement. 475 TimeStamp now = TimeStamp::Now(); 476 TimeDuration duration = now - mLastEventTime; 477 if (duration.ToMilliseconds() >= 478 StaticPrefs::mousewheel_transaction_ignoremovedelay()) { 479 mLastMouseMove = now; 480 } 481 } 482 } 483 484 void WheelBlockState::UpdateTargetApzc( 485 const RefPtr<AsyncPanZoomController>& aTargetApzc) { 486 InputBlockState::UpdateTargetApzc(aTargetApzc); 487 488 // If we found there was no target apzc, then we end the transaction. 489 if (!GetTargetApzc()) { 490 EndTransaction(); 491 } 492 } 493 494 bool WheelBlockState::InTransaction() const { 495 // We consider a wheel block to be in a transaction if it has a confirmed 496 // target and is the most recent wheel input block to be created. 497 if (GetBlockId() != sLastWheelBlockId) { 498 return false; 499 } 500 501 if (mTransactionEnded) { 502 return false; 503 } 504 505 MOZ_ASSERT(GetTargetApzc()); 506 return true; 507 } 508 509 bool WheelBlockState::AllowScrollHandoff() const { 510 // If we're in a wheel transaction, we do not allow overscroll handoff until 511 // a new event ends the wheel transaction. 512 return !IsTargetConfirmed() || !InTransaction(); 513 } 514 515 void WheelBlockState::EndTransaction() { 516 TBS_LOG("%p ending wheel transaction\n", this); 517 mTransactionEnded = true; 518 } 519 520 PanGestureBlockState::PanGestureBlockState( 521 const RefPtr<AsyncPanZoomController>& aTargetApzc, 522 TargetConfirmationFlags aFlags, const PanGestureInput& aInitialEvent) 523 : CancelableBlockState(aTargetApzc, aFlags), 524 mInterrupted(false), 525 mWaitingForContentResponse(false), 526 mWaitingForBrowserGestureResponse(false), 527 mStartedBrowserGesture(false) { 528 if (aFlags.mTargetConfirmed) { 529 // Find the nearest APZC in the overscroll handoff chain that is scrollable. 530 // If we get a content confirmation later that the apzc is different, then 531 // content should have found a scrollable apzc, so we don't need to handle 532 // that case. 533 RefPtr<AsyncPanZoomController> apzc = 534 mOverscrollHandoffChain->FindFirstScrollable(aInitialEvent, 535 &mAllowedScrollDirections); 536 537 if (apzc && apzc != GetTargetApzc()) { 538 UpdateTargetApzc(apzc); 539 } 540 } 541 } 542 543 bool PanGestureBlockState::SetConfirmedTargetApzc( 544 const RefPtr<AsyncPanZoomController>& aTargetApzc, 545 TargetConfirmationState aState, InputQueueIterator aFirstInput, 546 bool aForScrollbarDrag) { 547 // The APZC that we find via APZCCallbackHelpers may not be the same APZC 548 // ESM or OverscrollHandoff would have computed. Make sure we get the right 549 // one by looking for the first apzc the next pending event can scroll. 550 RefPtr<AsyncPanZoomController> apzc = aTargetApzc; 551 if (apzc && aFirstInput) { 552 RefPtr<AsyncPanZoomController> scrollableApzc = 553 apzc->BuildOverscrollHandoffChain()->FindFirstScrollable( 554 *aFirstInput->Input(), &mAllowedScrollDirections); 555 if (scrollableApzc) { 556 apzc = scrollableApzc; 557 } 558 } 559 560 InputBlockState::SetConfirmedTargetApzc(apzc, aState, aFirstInput, 561 aForScrollbarDrag); 562 return true; 563 } 564 565 bool PanGestureBlockState::MustStayActive() { return !mInterrupted; } 566 567 const char* PanGestureBlockState::Type() { return "pan gesture"; } 568 569 bool PanGestureBlockState::SetContentResponse(bool aPreventDefault) { 570 if (aPreventDefault) { 571 TBS_LOG("%p setting interrupted flag\n", this); 572 mInterrupted = true; 573 } 574 bool stateChanged = CancelableBlockState::SetContentResponse(aPreventDefault); 575 if (mWaitingForContentResponse) { 576 mWaitingForContentResponse = false; 577 stateChanged = true; 578 } 579 return stateChanged; 580 } 581 582 bool PanGestureBlockState::IsReadyForHandling() const { 583 if (!CancelableBlockState::IsReadyForHandling()) { 584 return false; 585 } 586 return !mWaitingForBrowserGestureResponse && 587 (!mWaitingForContentResponse || IsContentResponseTimerExpired()); 588 } 589 590 bool PanGestureBlockState::ShouldDropEvents() const { 591 return CancelableBlockState::ShouldDropEvents() || mStartedBrowserGesture; 592 } 593 594 bool PanGestureBlockState::TimeoutContentResponse() { 595 // Reset mWaitingForBrowserGestureResponse here so that we will not wait for 596 // the response forever. 597 mWaitingForBrowserGestureResponse = false; 598 return CancelableBlockState::TimeoutContentResponse(); 599 } 600 601 bool PanGestureBlockState::AllowScrollHandoff() const { return false; } 602 603 void PanGestureBlockState::SetNeedsToWaitForContentResponse( 604 bool aWaitForContentResponse) { 605 mWaitingForContentResponse = aWaitForContentResponse; 606 } 607 608 void PanGestureBlockState::SetNeedsToWaitForBrowserGestureResponse( 609 bool aWaitForBrowserGestureResponse) { 610 mWaitingForBrowserGestureResponse = aWaitForBrowserGestureResponse; 611 } 612 613 void PanGestureBlockState::SetBrowserGestureResponse( 614 BrowserGestureResponse aResponse) { 615 mWaitingForBrowserGestureResponse = false; 616 mStartedBrowserGesture = bool(aResponse); 617 } 618 619 Maybe<LayersId> PanGestureBlockState::WheelTransactionLayersId() const { 620 return TargetApzc() ? Some(TargetApzc()->GetLayersId()) : Nothing(); 621 } 622 623 PinchGestureBlockState::PinchGestureBlockState( 624 const RefPtr<AsyncPanZoomController>& aTargetApzc, 625 TargetConfirmationFlags aFlags) 626 : CancelableBlockState(aTargetApzc, aFlags), 627 mInterrupted(false), 628 mWaitingForContentResponse(false) {} 629 630 bool PinchGestureBlockState::MustStayActive() { return true; } 631 632 const char* PinchGestureBlockState::Type() { return "pinch gesture"; } 633 634 bool PinchGestureBlockState::SetContentResponse(bool aPreventDefault) { 635 if (aPreventDefault) { 636 TBS_LOG("%p setting interrupted flag\n", this); 637 mInterrupted = true; 638 } 639 bool stateChanged = CancelableBlockState::SetContentResponse(aPreventDefault); 640 if (mWaitingForContentResponse) { 641 mWaitingForContentResponse = false; 642 stateChanged = true; 643 } 644 return stateChanged; 645 } 646 647 bool PinchGestureBlockState::IsReadyForHandling() const { 648 if (!CancelableBlockState::IsReadyForHandling()) { 649 return false; 650 } 651 return !mWaitingForContentResponse || IsContentResponseTimerExpired(); 652 } 653 654 void PinchGestureBlockState::SetNeedsToWaitForContentResponse( 655 bool aWaitForContentResponse) { 656 mWaitingForContentResponse = aWaitForContentResponse; 657 } 658 659 TouchBlockState::TouchBlockState( 660 const RefPtr<AsyncPanZoomController>& aTargetApzc, 661 TargetConfirmationFlags aFlags, TouchCounter& aCounter) 662 : CancelableBlockState(aTargetApzc, aFlags), 663 mAllowedTouchBehaviorSet(false), 664 mDuringFastFling(false), 665 mInSlop(false), 666 mForLongTap(false), 667 mLongTapWasProcessed(false), 668 mIsWaitingLongTapResult(false), 669 mNeedsWaitTouchMove(false), 670 mSingleTapState(apz::SingleTapState::NotClick), 671 mTouchCounter(aCounter), 672 mStartTime(GetTargetApzc()->GetFrameTime().Time()) { 673 mOriginalTargetConfirmedState = mTargetConfirmed; 674 TBS_LOG("Creating %p\n", this); 675 } 676 677 bool TouchBlockState::SetAllowedTouchBehaviors( 678 const nsTArray<TouchBehaviorFlags>& aBehaviors) { 679 if (mAllowedTouchBehaviorSet) { 680 return false; 681 } 682 TBS_LOG("%p got allowed touch behaviours for %zu points\n", this, 683 aBehaviors.Length()); 684 mAllowedTouchBehaviors.AppendElements(aBehaviors); 685 mAllowedTouchBehaviorSet = true; 686 return true; 687 } 688 689 bool TouchBlockState::GetAllowedTouchBehaviors( 690 nsTArray<TouchBehaviorFlags>& aOutBehaviors) const { 691 if (!mAllowedTouchBehaviorSet) { 692 return false; 693 } 694 aOutBehaviors.AppendElements(mAllowedTouchBehaviors); 695 return true; 696 } 697 698 bool TouchBlockState::HasAllowedTouchBehaviors() const { 699 return mAllowedTouchBehaviorSet; 700 } 701 702 void TouchBlockState::CopyPropertiesFrom(const TouchBlockState& aOther) { 703 TBS_LOG("%p copying properties from %p\n", this, &aOther); 704 MOZ_ASSERT(aOther.mAllowedTouchBehaviorSet || 705 aOther.IsContentResponseTimerExpired()); 706 SetAllowedTouchBehaviors(aOther.mAllowedTouchBehaviors); 707 mTransformToApzc = aOther.mTransformToApzc; 708 } 709 710 bool TouchBlockState::IsReadyForHandling() const { 711 if (!CancelableBlockState::IsReadyForHandling()) { 712 return false; 713 } 714 715 if (mIsWaitingLongTapResult) { 716 return false; 717 } 718 719 return mAllowedTouchBehaviorSet || IsContentResponseTimerExpired(); 720 } 721 722 void TouchBlockState::SetDuringFastFling() { 723 TBS_LOG("%p setting fast-motion flag\n", this); 724 mDuringFastFling = true; 725 } 726 727 bool TouchBlockState::IsDuringFastFling() const { return mDuringFastFling; } 728 729 void TouchBlockState::SetSingleTapState(apz::SingleTapState aState) { 730 TBS_LOG("%p setting single-tap-state: %d\n", this, 731 static_cast<uint8_t>(aState)); 732 mSingleTapState = aState; 733 } 734 735 bool TouchBlockState::MustStayActive() { 736 // If this touch block is for long-tap, it doesn't need to be active after the 737 // block was processed, it will be taken over by the original touch block 738 // which will stay active. 739 return !mForLongTap || !IsReadyForHandling(); 740 } 741 742 const char* TouchBlockState::Type() { return "touch"; } 743 744 TimeDuration TouchBlockState::GetTimeSinceBlockStart() const { 745 return GetTargetApzc()->GetFrameTime().Time() - mStartTime; 746 } 747 748 void TouchBlockState::DispatchEvent(const InputData& aEvent) const { 749 MOZ_ASSERT(aEvent.mInputType == MULTITOUCH_INPUT); 750 mTouchCounter.Update(aEvent.AsMultiTouchInput()); 751 CancelableBlockState::DispatchEvent(aEvent); 752 } 753 754 bool TouchBlockState::TouchActionAllowsPinchZoom() const { 755 bool forceUserScalable = StaticPrefs::browser_ui_zoom_force_user_scalable(); 756 757 // Pointer events specification requires that all touch points allow zoom. 758 for (auto& behavior : mAllowedTouchBehaviors) { 759 if ( 760 // These flags represent 'touch-action: none'; if all of them are unset, 761 // we want to disable pinch zoom, even if forceUserScalable is true. 762 // This matches the behavior of other browsers. 763 !(behavior & AllowedTouchBehavior::PINCH_ZOOM) && 764 !(behavior & AllowedTouchBehavior::ANIMATING_ZOOM) && 765 !(behavior & AllowedTouchBehavior::VERTICAL_PAN) && 766 !(behavior & AllowedTouchBehavior::HORIZONTAL_PAN)) { 767 return false; 768 } 769 770 if (forceUserScalable) { 771 return true; 772 } 773 774 if (!(behavior & AllowedTouchBehavior::PINCH_ZOOM)) { 775 return false; 776 } 777 } 778 return true; 779 } 780 781 bool TouchBlockState::TouchActionAllowsDoubleTapZoom() const { 782 for (auto& behavior : mAllowedTouchBehaviors) { 783 if (!(behavior & AllowedTouchBehavior::ANIMATING_ZOOM)) { 784 return false; 785 } 786 } 787 return true; 788 } 789 790 bool TouchBlockState::TouchActionAllowsPanningX() const { 791 if (mAllowedTouchBehaviors.IsEmpty()) { 792 // Default to allowed 793 return true; 794 } 795 TouchBehaviorFlags flags = mAllowedTouchBehaviors[0]; 796 return (flags & AllowedTouchBehavior::HORIZONTAL_PAN); 797 } 798 799 bool TouchBlockState::TouchActionAllowsPanningY() const { 800 if (mAllowedTouchBehaviors.IsEmpty()) { 801 // Default to allowed 802 return true; 803 } 804 TouchBehaviorFlags flags = mAllowedTouchBehaviors[0]; 805 return (flags & AllowedTouchBehavior::VERTICAL_PAN); 806 } 807 808 bool TouchBlockState::TouchActionAllowsPanningXY() const { 809 if (mAllowedTouchBehaviors.IsEmpty()) { 810 // Default to allowed 811 return true; 812 } 813 TouchBehaviorFlags flags = mAllowedTouchBehaviors[0]; 814 return (flags & AllowedTouchBehavior::HORIZONTAL_PAN) && 815 (flags & AllowedTouchBehavior::VERTICAL_PAN); 816 } 817 818 bool TouchBlockState::UpdateSlopState(const MultiTouchInput& aInput, 819 bool aApzcCanConsumeEvents) { 820 if (aInput.mType == MultiTouchInput::MULTITOUCH_START) { 821 // this is by definition the first event in this block. If it's the first 822 // touch, then we enter a slop state. 823 mInSlop = (aInput.mTouches.Length() == 1); 824 if (mInSlop) { 825 mSlopOrigin = aInput.mTouches[0].mScreenPoint; 826 TBS_LOG("%p entering slop with origin %s\n", this, 827 ToString(mSlopOrigin).c_str()); 828 } 829 return false; 830 } 831 if (mInSlop) { 832 ScreenCoord threshold = 0; 833 // If the target was confirmed to null then the threshold doesn't 834 // matter anyway since the events will never be processed. 835 if (const RefPtr<AsyncPanZoomController>& apzc = GetTargetApzc()) { 836 threshold = aApzcCanConsumeEvents ? apzc->GetTouchStartTolerance() 837 : apzc->GetTouchMoveTolerance(); 838 } 839 bool stayInSlop = 840 (aInput.mType == MultiTouchInput::MULTITOUCH_MOVE) && 841 (aInput.mTouches.Length() == 1) && 842 ((aInput.mTouches[0].mScreenPoint - mSlopOrigin).Length() < threshold); 843 if (!stayInSlop) { 844 // we're out of the slop zone, and will stay out for the remainder of 845 // this block 846 TBS_LOG("%p exiting slop\n", this); 847 mInSlop = false; 848 } 849 } 850 return mInSlop; 851 } 852 853 bool TouchBlockState::IsInSlop() const { return mInSlop; } 854 855 Maybe<ScrollDirection> TouchBlockState::GetBestGuessPanDirection( 856 const MultiTouchInput& aInput) const { 857 if (aInput.mType != MultiTouchInput::MULTITOUCH_MOVE || 858 aInput.mTouches.Length() != 1) { 859 return Nothing(); 860 } 861 ScreenPoint vector = aInput.mTouches[0].mScreenPoint - mSlopOrigin; 862 double angle = atan2(vector.y, vector.x); // range [-pi, pi] 863 angle = fabs(angle); // range [0, pi] 864 865 double angleThreshold = TouchActionAllowsPanningXY() 866 ? StaticPrefs::apz_axis_lock_lock_angle() 867 : StaticPrefs::apz_axis_lock_direct_pan_angle(); 868 if (apz::IsCloseToHorizontal(angle, angleThreshold)) { 869 return Some(ScrollDirection::eHorizontal); 870 } 871 if (apz::IsCloseToVertical(angle, angleThreshold)) { 872 return Some(ScrollDirection::eVertical); 873 } 874 return Nothing(); 875 } 876 877 uint32_t TouchBlockState::GetActiveTouchCount() const { 878 return mTouchCounter.GetActiveTouchCount(); 879 } 880 881 bool TouchBlockState::IsTargetOriginallyConfirmed() const { 882 return mOriginalTargetConfirmedState != TargetConfirmationState::eUnconfirmed; 883 } 884 885 KeyboardBlockState::KeyboardBlockState( 886 const RefPtr<AsyncPanZoomController>& aTargetApzc) 887 : InputBlockState(aTargetApzc, TargetConfirmationFlags{true}) {} 888 889 } // namespace layers 890 } // namespace mozilla