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