tor-browser

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

InputQueue.cpp (49236B)


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