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