tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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