Bug 1719855 - Route preventDefaulted touchmove event information to APZ properly...
[gecko.git] / gfx / layers / apz / src / InputBlockState.cpp
blob181703232c9bd49d88a96f807d2664973931effc
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/. */
7 #include "InputBlockState.h"
9 #include "APZUtils.h"
10 #include "AsyncPanZoomController.h" // for AsyncPanZoomController
12 #include "mozilla/MouseEvents.h"
13 #include "mozilla/StaticPrefs_apz.h"
14 #include "mozilla/StaticPrefs_layout.h"
15 #include "mozilla/StaticPrefs_mousewheel.h"
16 #include "mozilla/StaticPrefs_test.h"
17 #include "mozilla/Telemetry.h" // for Telemetry
18 #include "mozilla/ToString.h"
19 #include "mozilla/layers/IAPZCTreeManager.h" // for AllowedTouchBehavior
20 #include "OverscrollHandoffState.h"
21 #include "QueuedInput.h"
23 static mozilla::LazyLogModule sApzIbsLog("apz.inputstate");
24 #define TBS_LOG(...) MOZ_LOG(sApzIbsLog, LogLevel::Debug, (__VA_ARGS__))
26 namespace mozilla {
27 namespace layers {
29 static uint64_t sBlockCounter = InputBlockState::NO_BLOCK_ID + 1;
31 InputBlockState::InputBlockState(
32 const RefPtr<AsyncPanZoomController>& aTargetApzc,
33 TargetConfirmationFlags aFlags)
34 : mTargetApzc(aTargetApzc),
35 mRequiresTargetConfirmation(aFlags.mRequiresTargetConfirmation),
36 mBlockId(sBlockCounter++),
37 mTransformToApzc(aTargetApzc->GetTransformToThis()) {
38 // We should never be constructed with a nullptr target.
39 MOZ_ASSERT(mTargetApzc);
40 mOverscrollHandoffChain = mTargetApzc->BuildOverscrollHandoffChain();
41 // If a new block starts on a scrollthumb and we have APZ scrollbar
42 // dragging enabled, defer confirmation until we get the drag metrics
43 // for the thumb.
44 bool startingDrag = StaticPrefs::apz_drag_enabled() && aFlags.mHitScrollThumb;
45 mTargetConfirmed = aFlags.mTargetConfirmed && !startingDrag
46 ? TargetConfirmationState::eConfirmed
47 : TargetConfirmationState::eUnconfirmed;
50 bool InputBlockState::SetConfirmedTargetApzc(
51 const RefPtr<AsyncPanZoomController>& aTargetApzc,
52 TargetConfirmationState aState, InputData* aFirstInput,
53 bool aForScrollbarDrag) {
54 MOZ_ASSERT(aState == TargetConfirmationState::eConfirmed ||
55 aState == TargetConfirmationState::eTimedOut);
57 // Sometimes, bugs in compositor hit testing can lead to APZ confirming
58 // a different target than the main thread. If this happens for a drag
59 // block created for a scrollbar drag, the consequences can be fairly
60 // user-unfriendly, such as the scrollbar not being draggable at all,
61 // or it scrolling the contents of the wrong scrollframe. In debug
62 // builds, we assert in this situation, so that the
63 // underlying compositor hit testing bug can be fixed. In release builds,
64 // however, we just silently accept the main thread's confirmed target,
65 // which will produce the expected behaviour (apart from drag events
66 // received so far being dropped).
67 if (AsDragBlock() && aForScrollbarDrag &&
68 mTargetConfirmed == TargetConfirmationState::eConfirmed &&
69 aState == TargetConfirmationState::eConfirmed && mTargetApzc &&
70 aTargetApzc && mTargetApzc->GetGuid() != aTargetApzc->GetGuid()) {
71 MOZ_ASSERT(false,
72 "APZ and main thread confirmed scrollbar drag block with "
73 "different targets");
74 UpdateTargetApzc(aTargetApzc);
75 return true;
78 if (mTargetConfirmed != TargetConfirmationState::eUnconfirmed) {
79 return false;
81 mTargetConfirmed = aState;
83 TBS_LOG("%p got confirmed target APZC %p\n", this, mTargetApzc.get());
84 if (mTargetApzc == aTargetApzc) {
85 // The confirmed target is the same as the tentative one, so we're done.
86 return true;
89 TBS_LOG("%p replacing unconfirmed target %p with real target %p\n", this,
90 mTargetApzc.get(), aTargetApzc.get());
92 UpdateTargetApzc(aTargetApzc);
93 return true;
96 void InputBlockState::UpdateTargetApzc(
97 const RefPtr<AsyncPanZoomController>& aTargetApzc) {
98 if (mTargetApzc == aTargetApzc) {
99 MOZ_ASSERT_UNREACHABLE(
100 "The new target APZC should be different from the old one");
101 return;
104 if (mTargetApzc) {
105 // Restore overscroll state on the previous target APZC and ancestor APZCs
106 // in the scroll handoff chain other than the new one.
107 mTargetApzc->SnapBackIfOverscrolled();
109 uint32_t i = mOverscrollHandoffChain->IndexOf(mTargetApzc) + 1;
110 for (; i < mOverscrollHandoffChain->Length(); i++) {
111 AsyncPanZoomController* apzc = mOverscrollHandoffChain->GetApzcAtIndex(i);
112 if (apzc != aTargetApzc) {
113 MOZ_ASSERT(!apzc->IsOverscrolled() ||
114 apzc->IsOverscrollAnimationRunning());
115 apzc->SnapBackIfOverscrolled();
120 // note that aTargetApzc MAY be null here.
121 mTargetApzc = aTargetApzc;
122 mTransformToApzc = aTargetApzc ? aTargetApzc->GetTransformToThis()
123 : ScreenToParentLayerMatrix4x4();
124 mOverscrollHandoffChain =
125 (mTargetApzc ? mTargetApzc->BuildOverscrollHandoffChain() : nullptr);
128 const RefPtr<AsyncPanZoomController>& InputBlockState::GetTargetApzc() const {
129 return mTargetApzc;
132 const RefPtr<const OverscrollHandoffChain>&
133 InputBlockState::GetOverscrollHandoffChain() const {
134 return mOverscrollHandoffChain;
137 uint64_t InputBlockState::GetBlockId() const { return mBlockId; }
139 bool InputBlockState::IsTargetConfirmed() const {
140 return mTargetConfirmed != TargetConfirmationState::eUnconfirmed;
143 bool InputBlockState::ShouldDropEvents() const {
144 return mRequiresTargetConfirmation &&
145 (mTargetConfirmed != TargetConfirmationState::eConfirmed);
148 bool InputBlockState::IsDownchainOf(AsyncPanZoomController* aA,
149 AsyncPanZoomController* aB) const {
150 if (aA == aB) {
151 return true;
154 bool seenA = false;
155 for (size_t i = 0; i < mOverscrollHandoffChain->Length(); ++i) {
156 AsyncPanZoomController* apzc = mOverscrollHandoffChain->GetApzcAtIndex(i);
157 if (apzc == aB) {
158 return seenA;
160 if (apzc == aA) {
161 seenA = true;
164 return false;
167 void InputBlockState::SetScrolledApzc(AsyncPanZoomController* aApzc) {
168 // An input block should only have one scrolled APZC.
169 MOZ_ASSERT(!mScrolledApzc || (StaticPrefs::apz_allow_immediate_handoff()
170 ? IsDownchainOf(mScrolledApzc, aApzc)
171 : mScrolledApzc == aApzc));
173 mScrolledApzc = aApzc;
176 AsyncPanZoomController* InputBlockState::GetScrolledApzc() const {
177 return mScrolledApzc;
180 bool InputBlockState::IsDownchainOfScrolledApzc(
181 AsyncPanZoomController* aApzc) const {
182 MOZ_ASSERT(aApzc && mScrolledApzc);
184 return IsDownchainOf(mScrolledApzc, aApzc);
187 void InputBlockState::DispatchEvent(const InputData& aEvent) const {
188 GetTargetApzc()->HandleInputEvent(aEvent, mTransformToApzc);
191 CancelableBlockState::CancelableBlockState(
192 const RefPtr<AsyncPanZoomController>& aTargetApzc,
193 TargetConfirmationFlags aFlags)
194 : InputBlockState(aTargetApzc, aFlags),
195 mPreventDefault(false),
196 mContentResponded(false),
197 mContentResponseTimerExpired(false) {}
199 bool CancelableBlockState::SetContentResponse(bool aPreventDefault) {
200 if (mContentResponded) {
201 return false;
203 TBS_LOG("%p got content response %d with timer expired %d\n", this,
204 aPreventDefault, mContentResponseTimerExpired);
205 mPreventDefault = aPreventDefault;
206 mContentResponded = true;
207 return true;
210 bool CancelableBlockState::TimeoutContentResponse() {
211 if (mContentResponseTimerExpired) {
212 return false;
214 TBS_LOG("%p got content timer expired with response received %d\n", this,
215 mContentResponded);
216 if (!mContentResponded) {
217 mPreventDefault = false;
219 mContentResponseTimerExpired = true;
220 return true;
223 bool CancelableBlockState::IsContentResponseTimerExpired() const {
224 return mContentResponseTimerExpired;
227 bool CancelableBlockState::IsDefaultPrevented() const {
228 MOZ_ASSERT(mContentResponded || mContentResponseTimerExpired);
229 return mPreventDefault;
232 bool CancelableBlockState::IsReadyForHandling() const {
233 if (!IsTargetConfirmed()) {
234 return false;
236 return mContentResponded || mContentResponseTimerExpired;
239 bool CancelableBlockState::ShouldDropEvents() const {
240 return InputBlockState::ShouldDropEvents() || IsDefaultPrevented();
243 DragBlockState::DragBlockState(
244 const RefPtr<AsyncPanZoomController>& aTargetApzc,
245 TargetConfirmationFlags aFlags, const MouseInput& aInitialEvent)
246 : CancelableBlockState(aTargetApzc, aFlags), mReceivedMouseUp(false) {}
248 bool DragBlockState::HasReceivedMouseUp() { return mReceivedMouseUp; }
250 void DragBlockState::MarkMouseUpReceived() { mReceivedMouseUp = true; }
252 void DragBlockState::SetInitialThumbPos(OuterCSSCoord aThumbPos) {
253 mInitialThumbPos = aThumbPos;
256 void DragBlockState::SetDragMetrics(const AsyncDragMetrics& aDragMetrics,
257 const CSSRect& aScrollableRect) {
258 mDragMetrics = aDragMetrics;
259 mInitialScrollableRect = aScrollableRect;
262 void DragBlockState::DispatchEvent(const InputData& aEvent) const {
263 MouseInput mouseInput = aEvent.AsMouseInput();
264 if (!mouseInput.TransformToLocal(mTransformToApzc)) {
265 return;
268 GetTargetApzc()->HandleDragEvent(mouseInput, mDragMetrics, mInitialThumbPos,
269 mInitialScrollableRect);
272 bool DragBlockState::MustStayActive() { return !mReceivedMouseUp; }
274 const char* DragBlockState::Type() { return "drag"; }
275 // This is used to track the current wheel transaction.
276 static uint64_t sLastWheelBlockId = InputBlockState::NO_BLOCK_ID;
278 WheelBlockState::WheelBlockState(
279 const RefPtr<AsyncPanZoomController>& aTargetApzc,
280 TargetConfirmationFlags aFlags, const ScrollWheelInput& aInitialEvent)
281 : CancelableBlockState(aTargetApzc, aFlags),
282 mScrollSeriesCounter(0),
283 mTransactionEnded(false) {
284 sLastWheelBlockId = GetBlockId();
286 if (aFlags.mTargetConfirmed) {
287 // Find the nearest APZC in the overscroll handoff chain that is scrollable.
288 // If we get a content confirmation later that the apzc is different, then
289 // content should have found a scrollable apzc, so we don't need to handle
290 // that case.
291 RefPtr<AsyncPanZoomController> apzc =
292 mOverscrollHandoffChain->FindFirstScrollable(aInitialEvent,
293 &mAllowedScrollDirections);
295 if (apzc) {
296 if (apzc != GetTargetApzc()) {
297 UpdateTargetApzc(apzc);
299 } else if (!mOverscrollHandoffChain->CanBePanned(
300 mOverscrollHandoffChain->GetApzcAtIndex(0))) {
301 // If there's absolutely nothing scrollable start a transaction and mark
302 // this as such to we know to store our EventTime.
303 mIsScrollable = false;
304 } else {
305 // Scrollable, but not in this direction.
306 EndTransaction();
311 bool WheelBlockState::SetContentResponse(bool aPreventDefault) {
312 if (aPreventDefault) {
313 EndTransaction();
315 return CancelableBlockState::SetContentResponse(aPreventDefault);
318 bool WheelBlockState::SetConfirmedTargetApzc(
319 const RefPtr<AsyncPanZoomController>& aTargetApzc,
320 TargetConfirmationState aState, InputData* aFirstInput,
321 bool aForScrollbarDrag) {
322 // The APZC that we find via APZCCallbackHelpers may not be the same APZC
323 // ESM or OverscrollHandoff would have computed. Make sure we get the right
324 // one by looking for the first apzc the next pending event can scroll.
325 RefPtr<AsyncPanZoomController> apzc = aTargetApzc;
326 if (apzc && aFirstInput) {
327 apzc = apzc->BuildOverscrollHandoffChain()->FindFirstScrollable(
328 *aFirstInput, &mAllowedScrollDirections);
331 InputBlockState::SetConfirmedTargetApzc(apzc, aState, aFirstInput,
332 aForScrollbarDrag);
333 return true;
336 void WheelBlockState::Update(ScrollWheelInput& aEvent) {
337 // We might not be in a transaction if the block never started in a
338 // transaction - for example, if nothing was scrollable.
339 if (!InTransaction()) {
340 return;
343 // The current "scroll series" is a like a sub-transaction. It has a separate
344 // timeout of 80ms. Since we need to compute wheel deltas at different phases
345 // of a transaction (for example, when it is updated, and later when the
346 // event action is taken), we affix the scroll series counter to the event.
347 // This makes GetScrollWheelDelta() consistent.
348 if (!mLastEventTime.IsNull() &&
349 (aEvent.mTimeStamp - mLastEventTime).ToMilliseconds() >
350 StaticPrefs::mousewheel_scroll_series_timeout()) {
351 mScrollSeriesCounter = 0;
353 aEvent.mScrollSeriesNumber = ++mScrollSeriesCounter;
355 // If we can't scroll in the direction of the wheel event, we don't update
356 // the last move time. This allows us to timeout a transaction even if the
357 // mouse isn't moving.
359 // We skip this check if the target is not yet confirmed, so that when it is
360 // confirmed, we don't timeout the transaction.
361 RefPtr<AsyncPanZoomController> apzc = GetTargetApzc();
362 if (mIsScrollable && IsTargetConfirmed() && !apzc->CanScroll(aEvent)) {
363 return;
366 // Update the time of the last known good event, and reset the mouse move
367 // time to null. This will reset the delays on both the general transaction
368 // timeout and the mouse-move-in-frame timeout.
369 mLastEventTime = aEvent.mTimeStamp;
370 mLastMouseMove = TimeStamp();
373 bool WheelBlockState::MustStayActive() { return !mTransactionEnded; }
375 const char* WheelBlockState::Type() { return "scroll wheel"; }
377 bool WheelBlockState::ShouldAcceptNewEvent() const {
378 if (!InTransaction()) {
379 // If we're not in a transaction, start a new one.
380 return false;
383 RefPtr<AsyncPanZoomController> apzc = GetTargetApzc();
384 if (apzc->IsDestroyed()) {
385 return false;
388 return true;
391 bool WheelBlockState::MaybeTimeout(const ScrollWheelInput& aEvent) {
392 MOZ_ASSERT(InTransaction());
394 if (MaybeTimeout(aEvent.mTimeStamp)) {
395 return true;
398 if (!mLastMouseMove.IsNull()) {
399 // If there's a recent mouse movement, we can time out the transaction
400 // early.
401 TimeDuration duration = TimeStamp::Now() - mLastMouseMove;
402 if (duration.ToMilliseconds() >=
403 StaticPrefs::mousewheel_transaction_ignoremovedelay()) {
404 TBS_LOG("%p wheel transaction timed out after mouse move\n", this);
405 EndTransaction();
406 return true;
410 return false;
413 bool WheelBlockState::MaybeTimeout(const TimeStamp& aTimeStamp) {
414 MOZ_ASSERT(InTransaction());
416 // End the transaction if the event occurred > 1.5s after the most recently
417 // seen wheel event.
418 TimeDuration duration = aTimeStamp - mLastEventTime;
419 if (duration.ToMilliseconds() <
420 StaticPrefs::mousewheel_transaction_timeout()) {
421 return false;
424 TBS_LOG("%p wheel transaction timed out\n", this);
426 if (StaticPrefs::test_mousescroll()) {
427 RefPtr<AsyncPanZoomController> apzc = GetTargetApzc();
428 apzc->NotifyMozMouseScrollEvent(u"MozMouseScrollTransactionTimeout"_ns);
431 EndTransaction();
432 return true;
435 void WheelBlockState::OnMouseMove(
436 const ScreenIntPoint& aPoint,
437 const Maybe<ScrollableLayerGuid>& aTargetGuid) {
438 MOZ_ASSERT(InTransaction());
440 if (!GetTargetApzc()->Contains(aPoint) ||
441 // If the mouse moved over to a different APZC, `mIsScrollable`
442 // may no longer be false and needs to be recomputed.
443 (!mIsScrollable && aTargetGuid.isSome() &&
444 aTargetGuid.value() != GetTargetApzc()->GetGuid())) {
445 EndTransaction();
446 return;
449 if (mLastMouseMove.IsNull()) {
450 // If the cursor is moving inside the frame, and it is more than the
451 // ignoremovedelay time since the last scroll operation, we record
452 // this as the most recent mouse movement.
453 TimeStamp now = TimeStamp::Now();
454 TimeDuration duration = now - mLastEventTime;
455 if (duration.ToMilliseconds() >=
456 StaticPrefs::mousewheel_transaction_ignoremovedelay()) {
457 mLastMouseMove = now;
462 void WheelBlockState::UpdateTargetApzc(
463 const RefPtr<AsyncPanZoomController>& aTargetApzc) {
464 InputBlockState::UpdateTargetApzc(aTargetApzc);
466 // If we found there was no target apzc, then we end the transaction.
467 if (!GetTargetApzc()) {
468 EndTransaction();
472 bool WheelBlockState::InTransaction() const {
473 // We consider a wheel block to be in a transaction if it has a confirmed
474 // target and is the most recent wheel input block to be created.
475 if (GetBlockId() != sLastWheelBlockId) {
476 return false;
479 if (mTransactionEnded) {
480 return false;
483 MOZ_ASSERT(GetTargetApzc());
484 return true;
487 bool WheelBlockState::AllowScrollHandoff() const {
488 // If we're in a wheel transaction, we do not allow overscroll handoff until
489 // a new event ends the wheel transaction.
490 return !IsTargetConfirmed() || !InTransaction();
493 void WheelBlockState::EndTransaction() {
494 TBS_LOG("%p ending wheel transaction\n", this);
495 mTransactionEnded = true;
498 PanGestureBlockState::PanGestureBlockState(
499 const RefPtr<AsyncPanZoomController>& aTargetApzc,
500 TargetConfirmationFlags aFlags, const PanGestureInput& aInitialEvent)
501 : CancelableBlockState(aTargetApzc, aFlags),
502 mInterrupted(false),
503 mWaitingForContentResponse(false),
504 mWaitingForBrowserGestureResponse(false),
505 mStartedBrowserGesture(false) {
506 if (aFlags.mTargetConfirmed) {
507 // Find the nearest APZC in the overscroll handoff chain that is scrollable.
508 // If we get a content confirmation later that the apzc is different, then
509 // content should have found a scrollable apzc, so we don't need to handle
510 // that case.
511 RefPtr<AsyncPanZoomController> apzc =
512 mOverscrollHandoffChain->FindFirstScrollable(aInitialEvent,
513 &mAllowedScrollDirections);
515 if (apzc && apzc != GetTargetApzc()) {
516 UpdateTargetApzc(apzc);
521 bool PanGestureBlockState::SetConfirmedTargetApzc(
522 const RefPtr<AsyncPanZoomController>& aTargetApzc,
523 TargetConfirmationState aState, InputData* aFirstInput,
524 bool aForScrollbarDrag) {
525 // The APZC that we find via APZCCallbackHelpers may not be the same APZC
526 // ESM or OverscrollHandoff would have computed. Make sure we get the right
527 // one by looking for the first apzc the next pending event can scroll.
528 RefPtr<AsyncPanZoomController> apzc = aTargetApzc;
529 if (apzc && aFirstInput) {
530 RefPtr<AsyncPanZoomController> scrollableApzc =
531 apzc->BuildOverscrollHandoffChain()->FindFirstScrollable(
532 *aFirstInput, &mAllowedScrollDirections);
533 if (scrollableApzc) {
534 apzc = scrollableApzc;
538 InputBlockState::SetConfirmedTargetApzc(apzc, aState, aFirstInput,
539 aForScrollbarDrag);
540 return true;
543 bool PanGestureBlockState::MustStayActive() { return !mInterrupted; }
545 const char* PanGestureBlockState::Type() { return "pan gesture"; }
547 bool PanGestureBlockState::SetContentResponse(bool aPreventDefault) {
548 if (aPreventDefault) {
549 TBS_LOG("%p setting interrupted flag\n", this);
550 mInterrupted = true;
552 bool stateChanged = CancelableBlockState::SetContentResponse(aPreventDefault);
553 if (mWaitingForContentResponse) {
554 mWaitingForContentResponse = false;
555 stateChanged = true;
557 return stateChanged;
560 bool PanGestureBlockState::IsReadyForHandling() const {
561 if (!CancelableBlockState::IsReadyForHandling()) {
562 return false;
564 return !mWaitingForBrowserGestureResponse &&
565 (!mWaitingForContentResponse || IsContentResponseTimerExpired());
568 bool PanGestureBlockState::ShouldDropEvents() const {
569 return CancelableBlockState::ShouldDropEvents() || mStartedBrowserGesture;
572 bool PanGestureBlockState::TimeoutContentResponse() {
573 // Reset mWaitingForBrowserGestureResponse here so that we will not wait for
574 // the response forever.
575 mWaitingForBrowserGestureResponse = false;
576 return CancelableBlockState::TimeoutContentResponse();
579 bool PanGestureBlockState::AllowScrollHandoff() const { return false; }
581 void PanGestureBlockState::SetNeedsToWaitForContentResponse(
582 bool aWaitForContentResponse) {
583 mWaitingForContentResponse = aWaitForContentResponse;
586 void PanGestureBlockState::SetNeedsToWaitForBrowserGestureResponse(
587 bool aWaitForBrowserGestureResponse) {
588 mWaitingForBrowserGestureResponse = aWaitForBrowserGestureResponse;
591 void PanGestureBlockState::SetBrowserGestureResponse(
592 BrowserGestureResponse aResponse) {
593 mWaitingForBrowserGestureResponse = false;
594 mStartedBrowserGesture = bool(aResponse);
597 PinchGestureBlockState::PinchGestureBlockState(
598 const RefPtr<AsyncPanZoomController>& aTargetApzc,
599 TargetConfirmationFlags aFlags)
600 : CancelableBlockState(aTargetApzc, aFlags),
601 mInterrupted(false),
602 mWaitingForContentResponse(false) {}
604 bool PinchGestureBlockState::MustStayActive() { return true; }
606 const char* PinchGestureBlockState::Type() { return "pinch gesture"; }
608 bool PinchGestureBlockState::SetContentResponse(bool aPreventDefault) {
609 if (aPreventDefault) {
610 TBS_LOG("%p setting interrupted flag\n", this);
611 mInterrupted = true;
613 bool stateChanged = CancelableBlockState::SetContentResponse(aPreventDefault);
614 if (mWaitingForContentResponse) {
615 mWaitingForContentResponse = false;
616 stateChanged = true;
618 return stateChanged;
621 bool PinchGestureBlockState::IsReadyForHandling() const {
622 if (!CancelableBlockState::IsReadyForHandling()) {
623 return false;
625 return !mWaitingForContentResponse || IsContentResponseTimerExpired();
628 void PinchGestureBlockState::SetNeedsToWaitForContentResponse(
629 bool aWaitForContentResponse) {
630 mWaitingForContentResponse = aWaitForContentResponse;
633 TouchBlockState::TouchBlockState(
634 const RefPtr<AsyncPanZoomController>& aTargetApzc,
635 TargetConfirmationFlags aFlags, TouchCounter& aCounter)
636 : CancelableBlockState(aTargetApzc, aFlags),
637 mAllowedTouchBehaviorSet(false),
638 mDuringFastFling(false),
639 mSingleTapOccurred(false),
640 mInSlop(false),
641 mForLongTap(false),
642 mLongTapWasProcessed(false),
643 mTouchCounter(aCounter),
644 mStartTime(GetTargetApzc()->GetFrameTime().Time()) {
645 mOriginalTargetConfirmedState = mTargetConfirmed;
646 TBS_LOG("Creating %p\n", this);
649 bool TouchBlockState::SetAllowedTouchBehaviors(
650 const nsTArray<TouchBehaviorFlags>& aBehaviors) {
651 if (mAllowedTouchBehaviorSet) {
652 return false;
654 TBS_LOG("%p got allowed touch behaviours for %zu points\n", this,
655 aBehaviors.Length());
656 mAllowedTouchBehaviors.AppendElements(aBehaviors);
657 mAllowedTouchBehaviorSet = true;
658 return true;
661 bool TouchBlockState::GetAllowedTouchBehaviors(
662 nsTArray<TouchBehaviorFlags>& aOutBehaviors) const {
663 if (!mAllowedTouchBehaviorSet) {
664 return false;
666 aOutBehaviors.AppendElements(mAllowedTouchBehaviors);
667 return true;
670 bool TouchBlockState::HasAllowedTouchBehaviors() const {
671 return mAllowedTouchBehaviorSet;
674 void TouchBlockState::CopyPropertiesFrom(const TouchBlockState& aOther) {
675 TBS_LOG("%p copying properties from %p\n", this, &aOther);
676 MOZ_ASSERT(aOther.mAllowedTouchBehaviorSet ||
677 aOther.IsContentResponseTimerExpired());
678 SetAllowedTouchBehaviors(aOther.mAllowedTouchBehaviors);
679 mTransformToApzc = aOther.mTransformToApzc;
682 bool TouchBlockState::IsReadyForHandling() const {
683 if (!CancelableBlockState::IsReadyForHandling()) {
684 return false;
687 return mAllowedTouchBehaviorSet || IsContentResponseTimerExpired();
690 void TouchBlockState::SetDuringFastFling() {
691 TBS_LOG("%p setting fast-motion flag\n", this);
692 mDuringFastFling = true;
695 bool TouchBlockState::IsDuringFastFling() const { return mDuringFastFling; }
697 void TouchBlockState::SetSingleTapOccurred() {
698 TBS_LOG("%p setting single-tap-occurred flag\n", this);
699 mSingleTapOccurred = true;
702 bool TouchBlockState::SingleTapOccurred() const { return mSingleTapOccurred; }
704 bool TouchBlockState::MustStayActive() {
705 // If this touch block is for long-tap, it doesn't need to be active after the
706 // block was processed, it will be taken over by the original touch block
707 // which will stay active.
708 return !mForLongTap || !IsReadyForHandling();
711 const char* TouchBlockState::Type() { return "touch"; }
713 TimeDuration TouchBlockState::GetTimeSinceBlockStart() const {
714 return GetTargetApzc()->GetFrameTime().Time() - mStartTime;
717 void TouchBlockState::DispatchEvent(const InputData& aEvent) const {
718 MOZ_ASSERT(aEvent.mInputType == MULTITOUCH_INPUT);
719 mTouchCounter.Update(aEvent.AsMultiTouchInput());
720 CancelableBlockState::DispatchEvent(aEvent);
723 bool TouchBlockState::TouchActionAllowsPinchZoom() const {
724 // Pointer events specification requires that all touch points allow zoom.
725 for (auto& behavior : mAllowedTouchBehaviors) {
726 if (!(behavior & AllowedTouchBehavior::PINCH_ZOOM)) {
727 return false;
730 return true;
733 bool TouchBlockState::TouchActionAllowsDoubleTapZoom() const {
734 for (auto& behavior : mAllowedTouchBehaviors) {
735 if (!(behavior & AllowedTouchBehavior::ANIMATING_ZOOM)) {
736 return false;
739 return true;
742 bool TouchBlockState::TouchActionAllowsPanningX() const {
743 if (mAllowedTouchBehaviors.IsEmpty()) {
744 // Default to allowed
745 return true;
747 TouchBehaviorFlags flags = mAllowedTouchBehaviors[0];
748 return (flags & AllowedTouchBehavior::HORIZONTAL_PAN);
751 bool TouchBlockState::TouchActionAllowsPanningY() const {
752 if (mAllowedTouchBehaviors.IsEmpty()) {
753 // Default to allowed
754 return true;
756 TouchBehaviorFlags flags = mAllowedTouchBehaviors[0];
757 return (flags & AllowedTouchBehavior::VERTICAL_PAN);
760 bool TouchBlockState::TouchActionAllowsPanningXY() const {
761 if (mAllowedTouchBehaviors.IsEmpty()) {
762 // Default to allowed
763 return true;
765 TouchBehaviorFlags flags = mAllowedTouchBehaviors[0];
766 return (flags & AllowedTouchBehavior::HORIZONTAL_PAN) &&
767 (flags & AllowedTouchBehavior::VERTICAL_PAN);
770 bool TouchBlockState::UpdateSlopState(const MultiTouchInput& aInput,
771 bool aApzcCanConsumeEvents) {
772 if (aInput.mType == MultiTouchInput::MULTITOUCH_START) {
773 // this is by definition the first event in this block. If it's the first
774 // touch, then we enter a slop state.
775 mInSlop = (aInput.mTouches.Length() == 1);
776 if (mInSlop) {
777 mSlopOrigin = aInput.mTouches[0].mScreenPoint;
778 TBS_LOG("%p entering slop with origin %s\n", this,
779 ToString(mSlopOrigin).c_str());
781 return false;
783 if (mInSlop) {
784 ScreenCoord threshold = 0;
785 // If the target was confirmed to null then the threshold doesn't
786 // matter anyway since the events will never be processed.
787 if (const RefPtr<AsyncPanZoomController>& apzc = GetTargetApzc()) {
788 threshold = aApzcCanConsumeEvents ? apzc->GetTouchStartTolerance()
789 : apzc->GetTouchMoveTolerance();
791 bool stayInSlop =
792 (aInput.mType == MultiTouchInput::MULTITOUCH_MOVE) &&
793 (aInput.mTouches.Length() == 1) &&
794 ((aInput.mTouches[0].mScreenPoint - mSlopOrigin).Length() < threshold);
795 if (!stayInSlop) {
796 // we're out of the slop zone, and will stay out for the remainder of
797 // this block
798 TBS_LOG("%p exiting slop\n", this);
799 mInSlop = false;
802 return mInSlop;
805 bool TouchBlockState::IsInSlop() const { return mInSlop; }
807 Maybe<ScrollDirection> TouchBlockState::GetBestGuessPanDirection(
808 const MultiTouchInput& aInput) {
809 if (aInput.mType != MultiTouchInput::MULTITOUCH_MOVE ||
810 aInput.mTouches.Length() != 1) {
811 return Nothing();
813 ScreenPoint vector = aInput.mTouches[0].mScreenPoint - mSlopOrigin;
814 double angle = atan2(vector.y, vector.x); // range [-pi, pi]
815 angle = fabs(angle); // range [0, pi]
817 double angleThreshold = TouchActionAllowsPanningXY()
818 ? StaticPrefs::apz_axis_lock_lock_angle()
819 : StaticPrefs::apz_axis_lock_direct_pan_angle();
820 if (apz::IsCloseToHorizontal(angle, angleThreshold)) {
821 return Some(ScrollDirection::eHorizontal);
823 if (apz::IsCloseToVertical(angle, angleThreshold)) {
824 return Some(ScrollDirection::eVertical);
826 return Nothing();
829 uint32_t TouchBlockState::GetActiveTouchCount() const {
830 return mTouchCounter.GetActiveTouchCount();
833 bool TouchBlockState::IsTargetOriginallyConfirmed() const {
834 return mOriginalTargetConfirmedState != TargetConfirmationState::eUnconfirmed;
837 KeyboardBlockState::KeyboardBlockState(
838 const RefPtr<AsyncPanZoomController>& aTargetApzc)
839 : InputBlockState(aTargetApzc, TargetConfirmationFlags{true}) {}
841 } // namespace layers
842 } // namespace mozilla