Source code

Revision control

Other Tools

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 "InputBlockState.h"
12
#include "LayersLogging.h"
13
#include "mozilla/layers/APZThreadUtils.h"
14
#include "OverscrollHandoffState.h"
15
#include "QueuedInput.h"
16
#include "mozilla/StaticPrefs_apz.h"
17
#include "mozilla/StaticPrefs_layout.h"
18
19
static mozilla::LazyLogModule sApzInpLog("apz.inputqueue");
20
#define INPQ_LOG(...) MOZ_LOG(sApzInpLog, LogLevel::Debug, (__VA_ARGS__))
21
22
namespace mozilla {
23
namespace layers {
24
25
InputQueue::InputQueue() = default;
26
27
InputQueue::~InputQueue() { mQueuedInputs.Clear(); }
28
29
nsEventStatus InputQueue::ReceiveInputEvent(
30
const RefPtr<AsyncPanZoomController>& aTarget,
31
TargetConfirmationFlags aFlags, const InputData& aEvent,
32
uint64_t* aOutInputBlockId,
33
const Maybe<nsTArray<TouchBehaviorFlags>>& aTouchBehaviors) {
34
APZThreadUtils::AssertOnControllerThread();
35
36
AutoRunImmediateTimeout timeoutRunner{this};
37
38
switch (aEvent.mInputType) {
39
case MULTITOUCH_INPUT: {
40
const MultiTouchInput& event = aEvent.AsMultiTouchInput();
41
return ReceiveTouchInput(aTarget, aFlags, event, aOutInputBlockId,
42
aTouchBehaviors);
43
}
44
45
case SCROLLWHEEL_INPUT: {
46
const ScrollWheelInput& event = aEvent.AsScrollWheelInput();
47
return ReceiveScrollWheelInput(aTarget, aFlags, event, aOutInputBlockId);
48
}
49
50
case PANGESTURE_INPUT: {
51
const PanGestureInput& event = aEvent.AsPanGestureInput();
52
return ReceivePanGestureInput(aTarget, aFlags, event, aOutInputBlockId);
53
}
54
55
case MOUSE_INPUT: {
56
const MouseInput& event = aEvent.AsMouseInput();
57
return ReceiveMouseInput(aTarget, aFlags, event, aOutInputBlockId);
58
}
59
60
case KEYBOARD_INPUT: {
61
// Every keyboard input must have a confirmed target
62
MOZ_ASSERT(aTarget && aFlags.mTargetConfirmed);
63
64
const KeyboardInput& event = aEvent.AsKeyboardInput();
65
return ReceiveKeyboardInput(aTarget, event, aOutInputBlockId);
66
}
67
68
default:
69
// The return value for non-touch input is only used by tests, so just
70
// pass through the return value for now. This can be changed later if
71
// needed.
72
// TODO (bug 1098430): we will eventually need to have smarter handling
73
// for non-touch events as well.
74
return aTarget->HandleInputEvent(aEvent, aTarget->GetTransformToThis());
75
}
76
}
77
78
nsEventStatus InputQueue::ReceiveTouchInput(
79
const RefPtr<AsyncPanZoomController>& aTarget,
80
TargetConfirmationFlags aFlags, const MultiTouchInput& aEvent,
81
uint64_t* aOutInputBlockId,
82
const Maybe<nsTArray<TouchBehaviorFlags>>& aTouchBehaviors) {
83
TouchBlockState* block = nullptr;
84
if (aEvent.mType == MultiTouchInput::MULTITOUCH_START) {
85
nsTArray<TouchBehaviorFlags> currentBehaviors;
86
bool haveBehaviors = false;
87
if (!StaticPrefs::layout_css_touch_action_enabled()) {
88
haveBehaviors = true;
89
} else if (mActiveTouchBlock) {
90
haveBehaviors =
91
mActiveTouchBlock->GetAllowedTouchBehaviors(currentBehaviors);
92
// If the behaviours aren't set, but the main-thread response timer on
93
// the block is expired we still treat it as though it has behaviors,
94
// because in that case we still want to interrupt the fast-fling and
95
// use the default behaviours.
96
haveBehaviors |= mActiveTouchBlock->IsContentResponseTimerExpired();
97
}
98
99
block = StartNewTouchBlock(aTarget, aFlags, false);
100
INPQ_LOG("started new touch block %p id %" PRIu64 " for target %p\n", block,
101
block->GetBlockId(), aTarget.get());
102
103
// XXX using the chain from |block| here may be wrong in cases where the
104
// target isn't confirmed and the real target turns out to be something
105
// else. For now assume this is rare enough that it's not an issue.
106
if (mQueuedInputs.IsEmpty() && aEvent.mTouches.Length() == 1 &&
107
block->GetOverscrollHandoffChain()->HasFastFlungApzc() &&
108
haveBehaviors) {
109
// If we're already in a fast fling, and a single finger goes down, then
110
// we want special handling for the touch event, because it shouldn't get
111
// delivered to content. Note that we don't set this flag when going
112
// from a fast fling to a pinch state (i.e. second finger goes down while
113
// the first finger is moving).
114
block->SetDuringFastFling();
115
block->SetConfirmedTargetApzc(
116
aTarget, InputBlockState::TargetConfirmationState::eConfirmed,
117
nullptr /* the block was just created so it has no events */,
118
false /* not a scrollbar drag */);
119
if (StaticPrefs::layout_css_touch_action_enabled()) {
120
block->SetAllowedTouchBehaviors(currentBehaviors);
121
}
122
INPQ_LOG("block %p tagged as fast-motion\n", block);
123
} else if (aTouchBehaviors) {
124
// If this block isn't started during a fast-fling, and APZCTM has
125
// provided touch behavior information, then put it on the block so
126
// that the ArePointerEventsConsumable call below can use it.
127
block->SetAllowedTouchBehaviors(*aTouchBehaviors);
128
}
129
130
CancelAnimationsForNewBlock(block);
131
132
MaybeRequestContentResponse(aTarget, block);
133
} else {
134
// for touch inputs that don't start a block, APZCTM shouldn't be giving
135
// us any touch behaviors.
136
MOZ_ASSERT(aTouchBehaviors.isNothing());
137
138
block = mActiveTouchBlock.get();
139
if (!block) {
140
NS_WARNING(
141
"Received a non-start touch event while no touch blocks active!");
142
return nsEventStatus_eIgnore;
143
}
144
145
INPQ_LOG("received new event in block %p\n", block);
146
}
147
148
if (aOutInputBlockId) {
149
*aOutInputBlockId = block->GetBlockId();
150
}
151
152
// Note that the |aTarget| the APZCTM sent us may contradict the confirmed
153
// target set on the block. In this case the confirmed target (which may be
154
// null) should take priority. This is equivalent to just always using the
155
// target (confirmed or not) from the block.
156
RefPtr<AsyncPanZoomController> target = block->GetTargetApzc();
157
158
nsEventStatus result = nsEventStatus_eIgnore;
159
160
// XXX calling ArePointerEventsConsumable on |target| may be wrong here if
161
// the target isn't confirmed and the real target turns out to be something
162
// else. For now assume this is rare enough that it's not an issue.
163
if (block->IsDuringFastFling()) {
164
INPQ_LOG("dropping event due to block %p being in fast motion\n", block);
165
result = nsEventStatus_eConsumeNoDefault;
166
} else if (target && target->ArePointerEventsConsumable(block, aEvent)) {
167
if (block->UpdateSlopState(aEvent, true)) {
168
INPQ_LOG("dropping event due to block %p being in slop\n", block);
169
result = nsEventStatus_eConsumeNoDefault;
170
} else {
171
result = nsEventStatus_eConsumeDoDefault;
172
}
173
} else if (block->UpdateSlopState(aEvent, false)) {
174
INPQ_LOG("dropping event due to block %p being in mini-slop\n", block);
175
result = nsEventStatus_eConsumeNoDefault;
176
}
177
mQueuedInputs.AppendElement(MakeUnique<QueuedInput>(aEvent, *block));
178
ProcessQueue();
179
return result;
180
}
181
182
nsEventStatus InputQueue::ReceiveMouseInput(
183
const RefPtr<AsyncPanZoomController>& aTarget,
184
TargetConfirmationFlags aFlags, const MouseInput& aEvent,
185
uint64_t* aOutInputBlockId) {
186
// On a new mouse down we can have a new target so we must force a new block
187
// with a new target.
188
bool newBlock = DragTracker::StartsDrag(aEvent);
189
190
DragBlockState* block = newBlock ? nullptr : mActiveDragBlock.get();
191
if (block && block->HasReceivedMouseUp()) {
192
block = nullptr;
193
}
194
195
if (!block && mDragTracker.InDrag()) {
196
// If there's no current drag block, but we're getting a move with a button
197
// down, we need to start a new drag block because we're obviously already
198
// in the middle of a drag (it probably got interrupted by something else).
199
INPQ_LOG(
200
"got a drag event outside a drag block, need to create a block to hold "
201
"it\n");
202
newBlock = true;
203
}
204
205
mDragTracker.Update(aEvent);
206
207
if (!newBlock && !block) {
208
// This input event is not in a drag block, so we're not doing anything
209
// with it, return eIgnore.
210
return nsEventStatus_eIgnore;
211
}
212
213
if (!block) {
214
MOZ_ASSERT(newBlock);
215
block = new DragBlockState(aTarget, aFlags, aEvent);
216
217
INPQ_LOG("started new drag block %p id %" PRIu64
218
" for %sconfirmed target %p\n",
219
block, block->GetBlockId(), aFlags.mTargetConfirmed ? "" : "un",
220
aTarget.get());
221
222
mActiveDragBlock = block;
223
224
CancelAnimationsForNewBlock(block);
225
MaybeRequestContentResponse(aTarget, block);
226
}
227
228
if (aOutInputBlockId) {
229
*aOutInputBlockId = block->GetBlockId();
230
}
231
232
mQueuedInputs.AppendElement(MakeUnique<QueuedInput>(aEvent, *block));
233
ProcessQueue();
234
235
if (DragTracker::EndsDrag(aEvent)) {
236
block->MarkMouseUpReceived();
237
}
238
239
// The event is part of a drag block and could potentially cause
240
// scrolling, so return DoDefault.
241
return nsEventStatus_eConsumeDoDefault;
242
}
243
244
nsEventStatus InputQueue::ReceiveScrollWheelInput(
245
const RefPtr<AsyncPanZoomController>& aTarget,
246
TargetConfirmationFlags aFlags, const ScrollWheelInput& aEvent,
247
uint64_t* aOutInputBlockId) {
248
WheelBlockState* block = mActiveWheelBlock.get();
249
// If the block is not accepting new events we'll create a new input block
250
// (and therefore a new wheel transaction).
251
if (block &&
252
(!block->ShouldAcceptNewEvent() || block->MaybeTimeout(aEvent))) {
253
block = nullptr;
254
}
255
256
MOZ_ASSERT(!block || block->InTransaction());
257
258
if (!block) {
259
block = new WheelBlockState(aTarget, aFlags, aEvent);
260
INPQ_LOG("started new scroll wheel block %p id %" PRIu64
261
" for %starget %p\n",
262
block, block->GetBlockId(),
263
aFlags.mTargetConfirmed ? "confirmed " : "", aTarget.get());
264
265
mActiveWheelBlock = block;
266
267
CancelAnimationsForNewBlock(block, ExcludeWheel);
268
MaybeRequestContentResponse(aTarget, block);
269
} else {
270
INPQ_LOG("received new event in block %p\n", block);
271
}
272
273
if (aOutInputBlockId) {
274
*aOutInputBlockId = block->GetBlockId();
275
}
276
277
// Note that the |aTarget| the APZCTM sent us may contradict the confirmed
278
// target set on the block. In this case the confirmed target (which may be
279
// null) should take priority. This is equivalent to just always using the
280
// target (confirmed or not) from the block, which is what
281
// ProcessQueue() does.
282
mQueuedInputs.AppendElement(MakeUnique<QueuedInput>(aEvent, *block));
283
284
// The WheelBlockState needs to affix a counter to the event before we process
285
// it. Note that the counter is affixed to the copy in the queue rather than
286
// |aEvent|.
287
block->Update(mQueuedInputs.LastElement()->Input()->AsScrollWheelInput());
288
289
ProcessQueue();
290
291
return nsEventStatus_eConsumeDoDefault;
292
}
293
294
nsEventStatus InputQueue::ReceiveKeyboardInput(
295
const RefPtr<AsyncPanZoomController>& aTarget, const KeyboardInput& aEvent,
296
uint64_t* aOutInputBlockId) {
297
KeyboardBlockState* block = mActiveKeyboardBlock.get();
298
299
// If the block is targeting a different Apzc than this keyboard event then
300
// we'll create a new input block
301
if (block && block->GetTargetApzc() != aTarget) {
302
block = nullptr;
303
}
304
305
if (!block) {
306
block = new KeyboardBlockState(aTarget);
307
INPQ_LOG("started new keyboard block %p id %" PRIu64 " for target %p\n",
308
block, block->GetBlockId(), aTarget.get());
309
310
mActiveKeyboardBlock = block;
311
} else {
312
INPQ_LOG("received new event in block %p\n", block);
313
}
314
315
if (aOutInputBlockId) {
316
*aOutInputBlockId = block->GetBlockId();
317
}
318
319
mQueuedInputs.AppendElement(MakeUnique<QueuedInput>(aEvent, *block));
320
321
ProcessQueue();
322
323
// If APZ is allowing passive listeners then we must dispatch the event to
324
// content, otherwise we can consume the event.
325
return StaticPrefs::apz_keyboard_passive_listeners()
326
? nsEventStatus_eConsumeDoDefault
327
: nsEventStatus_eConsumeNoDefault;
328
}
329
330
static bool CanScrollTargetHorizontally(const PanGestureInput& aInitialEvent,
331
PanGestureBlockState* aBlock) {
332
PanGestureInput horizontalComponent = aInitialEvent;
333
horizontalComponent.mPanDisplacement.y = 0;
334
ScrollDirections allowedScrollDirections;
335
RefPtr<AsyncPanZoomController> horizontallyScrollableAPZC =
336
aBlock->GetOverscrollHandoffChain()->FindFirstScrollable(
337
horizontalComponent, &allowedScrollDirections);
338
return horizontallyScrollableAPZC &&
339
horizontallyScrollableAPZC == aBlock->GetTargetApzc() &&
340
allowedScrollDirections.contains(ScrollDirection::eHorizontal);
341
}
342
343
nsEventStatus InputQueue::ReceivePanGestureInput(
344
const RefPtr<AsyncPanZoomController>& aTarget,
345
TargetConfirmationFlags aFlags, const PanGestureInput& aEvent,
346
uint64_t* aOutInputBlockId) {
347
if (aEvent.mType == PanGestureInput::PANGESTURE_MAYSTART ||
348
aEvent.mType == PanGestureInput::PANGESTURE_CANCELLED) {
349
// Ignore these events for now.
350
return nsEventStatus_eConsumeDoDefault;
351
}
352
353
PanGestureBlockState* block = nullptr;
354
if (aEvent.mType != PanGestureInput::PANGESTURE_START) {
355
block = mActivePanGestureBlock.get();
356
}
357
358
PanGestureInput event = aEvent;
359
nsEventStatus result = nsEventStatus_eConsumeDoDefault;
360
361
if (!block || block->WasInterrupted()) {
362
if (event.mType != PanGestureInput::PANGESTURE_START) {
363
// Only PANGESTURE_START events are allowed to start a new pan gesture
364
// block, but we really want to start a new block here, so we magically
365
// turn this input into a PANGESTURE_START.
366
INPQ_LOG(
367
"transmogrifying pan input %d to PANGESTURE_START for new block\n",
368
event.mType);
369
event.mType = PanGestureInput::PANGESTURE_START;
370
}
371
block = new PanGestureBlockState(aTarget, aFlags, event);
372
INPQ_LOG("started new pan gesture block %p id %" PRIu64 " for target %p\n",
373
block, block->GetBlockId(), aTarget.get());
374
375
if (aFlags.mTargetConfirmed &&
376
event
377
.mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection &&
378
!CanScrollTargetHorizontally(event, block)) {
379
// This event may trigger a swipe gesture, depending on what our caller
380
// wants to do it. We need to suspend handling of this block until we get
381
// a content response which will tell us whether to proceed or abort the
382
// block.
383
block->SetNeedsToWaitForContentResponse(true);
384
385
// Inform our caller that we haven't scrolled in response to the event
386
// and that a swipe can be started from this event if desired.
387
result = nsEventStatus_eIgnore;
388
}
389
390
mActivePanGestureBlock = block;
391
392
CancelAnimationsForNewBlock(block);
393
MaybeRequestContentResponse(aTarget, block);
394
} else {
395
INPQ_LOG("received new event in block %p\n", block);
396
}
397
398
if (aOutInputBlockId) {
399
*aOutInputBlockId = block->GetBlockId();
400
}
401
402
// Note that the |aTarget| the APZCTM sent us may contradict the confirmed
403
// target set on the block. In this case the confirmed target (which may be
404
// null) should take priority. This is equivalent to just always using the
405
// target (confirmed or not) from the block, which is what
406
// ProcessQueue() does.
407
mQueuedInputs.AppendElement(MakeUnique<QueuedInput>(event, *block));
408
ProcessQueue();
409
410
return result;
411
}
412
413
void InputQueue::CancelAnimationsForNewBlock(InputBlockState* aBlock,
414
CancelAnimationFlags aExtraFlags) {
415
// We want to cancel animations here as soon as possible (i.e. without waiting
416
// for content responses) because a finger has gone down and we don't want to
417
// keep moving the content under the finger. However, to prevent "future"
418
// touchstart events from interfering with "past" animations (i.e. from a
419
// previous touch block that is still being processed) we only do this
420
// animation-cancellation if there are no older touch blocks still in the
421
// queue.
422
if (mQueuedInputs.IsEmpty()) {
423
aBlock->GetOverscrollHandoffChain()->CancelAnimations(
424
aExtraFlags | ExcludeOverscroll | ScrollSnap);
425
}
426
}
427
428
void InputQueue::MaybeRequestContentResponse(
429
const RefPtr<AsyncPanZoomController>& aTarget,
430
CancelableBlockState* aBlock) {
431
bool waitForMainThread = false;
432
if (aBlock->IsTargetConfirmed()) {
433
// Content won't prevent-default this, so we can just set the flag directly.
434
INPQ_LOG("not waiting for content response on block %p\n", aBlock);
435
aBlock->SetContentResponse(false);
436
} else {
437
waitForMainThread = true;
438
}
439
if (aBlock->AsTouchBlock() &&
440
!aBlock->AsTouchBlock()->HasAllowedTouchBehaviors()) {
441
INPQ_LOG("waiting for main thread touch-action info on block %p\n", aBlock);
442
waitForMainThread = true;
443
}
444
if (waitForMainThread) {
445
// We either don't know for sure if aTarget is the right APZC, or we may
446
// need to wait to give content the opportunity to prevent-default the
447
// touch events. Either way we schedule a timeout so the main thread stuff
448
// can run.
449
ScheduleMainThreadTimeout(aTarget, aBlock);
450
}
451
}
452
453
uint64_t InputQueue::InjectNewTouchBlock(AsyncPanZoomController* aTarget) {
454
AutoRunImmediateTimeout timeoutRunner{this};
455
TouchBlockState* block =
456
StartNewTouchBlock(aTarget, TargetConfirmationFlags{true},
457
/* aCopyPropertiesFromCurrent = */ true);
458
INPQ_LOG("injecting new touch block %p with id %" PRIu64 " and target %p\n",
459
block, block->GetBlockId(), aTarget);
460
ScheduleMainThreadTimeout(aTarget, block);
461
return block->GetBlockId();
462
}
463
464
TouchBlockState* InputQueue::StartNewTouchBlock(
465
const RefPtr<AsyncPanZoomController>& aTarget,
466
TargetConfirmationFlags aFlags, bool aCopyPropertiesFromCurrent) {
467
TouchBlockState* newBlock =
468
new TouchBlockState(aTarget, aFlags, mTouchCounter);
469
if (aCopyPropertiesFromCurrent) {
470
// We should never enter here without a current touch block, because this
471
// codepath is invoked from the OnLongPress handler in
472
// AsyncPanZoomController, which should bail out if there is no current
473
// touch block.
474
MOZ_ASSERT(GetCurrentTouchBlock());
475
newBlock->CopyPropertiesFrom(*GetCurrentTouchBlock());
476
}
477
478
mActiveTouchBlock = newBlock;
479
return newBlock;
480
}
481
482
InputBlockState* InputQueue::GetCurrentBlock() const {
483
APZThreadUtils::AssertOnControllerThread();
484
return mQueuedInputs.IsEmpty() ? nullptr : mQueuedInputs[0]->Block();
485
}
486
487
TouchBlockState* InputQueue::GetCurrentTouchBlock() const {
488
InputBlockState* block = GetCurrentBlock();
489
return block ? block->AsTouchBlock() : mActiveTouchBlock.get();
490
}
491
492
WheelBlockState* InputQueue::GetCurrentWheelBlock() const {
493
InputBlockState* block = GetCurrentBlock();
494
return block ? block->AsWheelBlock() : mActiveWheelBlock.get();
495
}
496
497
DragBlockState* InputQueue::GetCurrentDragBlock() const {
498
InputBlockState* block = GetCurrentBlock();
499
return block ? block->AsDragBlock() : mActiveDragBlock.get();
500
}
501
502
PanGestureBlockState* InputQueue::GetCurrentPanGestureBlock() const {
503
InputBlockState* block = GetCurrentBlock();
504
return block ? block->AsPanGestureBlock() : mActivePanGestureBlock.get();
505
}
506
507
KeyboardBlockState* InputQueue::GetCurrentKeyboardBlock() const {
508
InputBlockState* block = GetCurrentBlock();
509
return block ? block->AsKeyboardBlock() : mActiveKeyboardBlock.get();
510
}
511
512
WheelBlockState* InputQueue::GetActiveWheelTransaction() const {
513
WheelBlockState* block = mActiveWheelBlock.get();
514
if (!block || !block->InTransaction()) {
515
return nullptr;
516
}
517
return block;
518
}
519
520
bool InputQueue::HasReadyTouchBlock() const {
521
return !mQueuedInputs.IsEmpty() &&
522
mQueuedInputs[0]->Block()->AsTouchBlock() &&
523
mQueuedInputs[0]->Block()->AsTouchBlock()->IsReadyForHandling();
524
}
525
526
bool InputQueue::AllowScrollHandoff() const {
527
if (GetCurrentWheelBlock()) {
528
return GetCurrentWheelBlock()->AllowScrollHandoff();
529
}
530
if (GetCurrentPanGestureBlock()) {
531
return GetCurrentPanGestureBlock()->AllowScrollHandoff();
532
}
533
if (GetCurrentKeyboardBlock()) {
534
return GetCurrentKeyboardBlock()->AllowScrollHandoff();
535
}
536
return true;
537
}
538
539
bool InputQueue::IsDragOnScrollbar(bool aHitScrollbar) {
540
if (!mDragTracker.InDrag()) {
541
return false;
542
}
543
// Now that we know we are in a drag, get the info from the drag tracker.
544
// We keep it in the tracker rather than the block because the block can get
545
// interrupted by something else (like a wheel event) and then a new block
546
// will get created without the info we want. The tracker will persist though.
547
return mDragTracker.IsOnScrollbar(aHitScrollbar);
548
}
549
550
void InputQueue::ScheduleMainThreadTimeout(
551
const RefPtr<AsyncPanZoomController>& aTarget,
552
CancelableBlockState* aBlock) {
553
INPQ_LOG("scheduling main thread timeout for target %p\n", aTarget.get());
554
aBlock->StartContentResponseTimer();
555
RefPtr<Runnable> timeoutTask = NewRunnableMethod<uint64_t>(
556
"layers::InputQueue::MainThreadTimeout", this,
557
&InputQueue::MainThreadTimeout, aBlock->GetBlockId());
558
int32_t timeout = StaticPrefs::apz_content_response_timeout();
559
if (timeout == 0) {
560
// If the timeout is zero, treat it as a request to ignore any main
561
// thread confirmation and unconditionally use fallback behaviour for
562
// when a timeout is reached. This codepath is used by tests that
563
// want to exercise the fallback behaviour.
564
// To ensure the fallback behaviour is used unconditionally, the timeout
565
// is run right away instead of using PostDelayedTask(). However,
566
// we can't run it right here, because MainThreadTimeout() expects that
567
// the input block has at least one input event in mQueuedInputs, and
568
// the event that triggered this call may not have been added to
569
// mQueuedInputs yet.
570
mImmediateTimeout = std::move(timeoutTask);
571
} else {
572
aTarget->PostDelayedTask(timeoutTask.forget(), timeout);
573
}
574
}
575
576
InputBlockState* InputQueue::FindBlockForId(uint64_t aInputBlockId,
577
InputData** aOutFirstInput) {
578
for (const auto& queuedInput : mQueuedInputs) {
579
if (queuedInput->Block()->GetBlockId() == aInputBlockId) {
580
if (aOutFirstInput) {
581
*aOutFirstInput = queuedInput->Input();
582
}
583
return queuedInput->Block();
584
}
585
}
586
587
InputBlockState* block = nullptr;
588
if (mActiveTouchBlock && mActiveTouchBlock->GetBlockId() == aInputBlockId) {
589
block = mActiveTouchBlock.get();
590
} else if (mActiveWheelBlock &&
591
mActiveWheelBlock->GetBlockId() == aInputBlockId) {
592
block = mActiveWheelBlock.get();
593
} else if (mActiveDragBlock &&
594
mActiveDragBlock->GetBlockId() == aInputBlockId) {
595
block = mActiveDragBlock.get();
596
} else if (mActivePanGestureBlock &&
597
mActivePanGestureBlock->GetBlockId() == aInputBlockId) {
598
block = mActivePanGestureBlock.get();
599
} else if (mActiveKeyboardBlock &&
600
mActiveKeyboardBlock->GetBlockId() == aInputBlockId) {
601
block = mActiveKeyboardBlock.get();
602
}
603
// Since we didn't encounter this block while iterating through mQueuedInputs,
604
// it must have no events associated with it at the moment.
605
if (aOutFirstInput) {
606
*aOutFirstInput = nullptr;
607
}
608
return block;
609
}
610
611
void InputQueue::MainThreadTimeout(uint64_t aInputBlockId) {
612
APZThreadUtils::AssertOnControllerThread();
613
614
INPQ_LOG("got a main thread timeout; block=%" PRIu64 "\n", aInputBlockId);
615
bool success = false;
616
InputData* firstInput = nullptr;
617
InputBlockState* inputBlock = FindBlockForId(aInputBlockId, &firstInput);
618
if (inputBlock && inputBlock->AsCancelableBlock()) {
619
CancelableBlockState* block = inputBlock->AsCancelableBlock();
620
// time out the touch-listener response and also confirm the existing
621
// target apzc in the case where the main thread doesn't get back to us
622
// fast enough.
623
success = block->TimeoutContentResponse();
624
success |= block->SetConfirmedTargetApzc(
625
block->GetTargetApzc(),
626
InputBlockState::TargetConfirmationState::eTimedOut, firstInput,
627
// This actually could be a scrollbar drag, but we pass
628
// aForScrollbarDrag=false because for scrollbar drags,
629
// SetConfirmedTargetApzc() will also be called by ConfirmDragBlock(),
630
// and we pass aForScrollbarDrag=true there.
631
false);
632
} else if (inputBlock) {
633
NS_WARNING("input block is not a cancelable block");
634
}
635
if (success) {
636
ProcessQueue();
637
}
638
}
639
640
void InputQueue::ContentReceivedInputBlock(uint64_t aInputBlockId,
641
bool aPreventDefault) {
642
APZThreadUtils::AssertOnControllerThread();
643
644
INPQ_LOG("got a content response; block=%" PRIu64 "\n", aInputBlockId);
645
bool success = false;
646
InputBlockState* inputBlock = FindBlockForId(aInputBlockId, nullptr);
647
if (inputBlock && inputBlock->AsCancelableBlock()) {
648
CancelableBlockState* block = inputBlock->AsCancelableBlock();
649
success = block->SetContentResponse(aPreventDefault);
650
block->RecordContentResponseTime();
651
} else if (inputBlock) {
652
NS_WARNING("input block is not a cancelable block");
653
}
654
if (success) {
655
ProcessQueue();
656
}
657
}
658
659
void InputQueue::SetConfirmedTargetApzc(
660
uint64_t aInputBlockId, const RefPtr<AsyncPanZoomController>& aTargetApzc) {
661
APZThreadUtils::AssertOnControllerThread();
662
663
INPQ_LOG("got a target apzc; block=%" PRIu64 " guid=%s\n", aInputBlockId,
664
aTargetApzc ? Stringify(aTargetApzc->GetGuid()).c_str() : "");
665
bool success = false;
666
InputData* firstInput = nullptr;
667
InputBlockState* inputBlock = FindBlockForId(aInputBlockId, &firstInput);
668
if (inputBlock && inputBlock->AsCancelableBlock()) {
669
CancelableBlockState* block = inputBlock->AsCancelableBlock();
670
success = block->SetConfirmedTargetApzc(
671
aTargetApzc, InputBlockState::TargetConfirmationState::eConfirmed,
672
firstInput,
673
// This actually could be a scrollbar drag, but we pass
674
// aForScrollbarDrag=false because for scrollbar drags,
675
// SetConfirmedTargetApzc() will also be called by ConfirmDragBlock(),
676
// and we pass aForScrollbarDrag=true there.
677
false);
678
block->RecordContentResponseTime();
679
} else if (inputBlock) {
680
NS_WARNING("input block is not a cancelable block");
681
}
682
if (success) {
683
ProcessQueue();
684
}
685
}
686
687
void InputQueue::ConfirmDragBlock(
688
uint64_t aInputBlockId, const RefPtr<AsyncPanZoomController>& aTargetApzc,
689
const AsyncDragMetrics& aDragMetrics) {
690
APZThreadUtils::AssertOnControllerThread();
691
692
INPQ_LOG("got a target apzc; block=%" PRIu64 " guid=%s dragtarget=%" PRIu64
693
"\n",
694
aInputBlockId,
695
aTargetApzc ? Stringify(aTargetApzc->GetGuid()).c_str() : "",
696
aDragMetrics.mViewId);
697
bool success = false;
698
InputData* firstInput = nullptr;
699
InputBlockState* inputBlock = FindBlockForId(aInputBlockId, &firstInput);
700
if (inputBlock && inputBlock->AsDragBlock()) {
701
DragBlockState* block = inputBlock->AsDragBlock();
702
block->SetDragMetrics(aDragMetrics);
703
success = block->SetConfirmedTargetApzc(
704
aTargetApzc, InputBlockState::TargetConfirmationState::eConfirmed,
705
firstInput,
706
/* aForScrollbarDrag = */ true);
707
block->RecordContentResponseTime();
708
}
709
if (success) {
710
ProcessQueue();
711
}
712
}
713
714
void InputQueue::SetAllowedTouchBehavior(
715
uint64_t aInputBlockId, const nsTArray<TouchBehaviorFlags>& aBehaviors) {
716
APZThreadUtils::AssertOnControllerThread();
717
718
INPQ_LOG("got allowed touch behaviours; block=%" PRIu64 "\n", aInputBlockId);
719
bool success = false;
720
InputBlockState* inputBlock = FindBlockForId(aInputBlockId, nullptr);
721
if (inputBlock && inputBlock->AsTouchBlock()) {
722
TouchBlockState* block = inputBlock->AsTouchBlock();
723
success = block->SetAllowedTouchBehaviors(aBehaviors);
724
block->RecordContentResponseTime();
725
} else if (inputBlock) {
726
NS_WARNING("input block is not a touch block");
727
}
728
if (success) {
729
ProcessQueue();
730
}
731
}
732
733
void InputQueue::ProcessQueue() {
734
APZThreadUtils::AssertOnControllerThread();
735
736
while (!mQueuedInputs.IsEmpty()) {
737
InputBlockState* curBlock = mQueuedInputs[0]->Block();
738
CancelableBlockState* cancelable = curBlock->AsCancelableBlock();
739
if (cancelable && !cancelable->IsReadyForHandling()) {
740
break;
741
}
742
743
INPQ_LOG(
744
"processing input from block %p; preventDefault %d shouldDropEvents %d "
745
"target %p\n",
746
curBlock, cancelable && cancelable->IsDefaultPrevented(),
747
curBlock->ShouldDropEvents(), curBlock->GetTargetApzc().get());
748
RefPtr<AsyncPanZoomController> target = curBlock->GetTargetApzc();
749
// target may be null here if the initial target was unconfirmed and then
750
// we later got a confirmed null target. in that case drop the events.
751
if (target) {
752
// If the event is targeting a different APZC than the previous one,
753
// we want to clear the previous APZC's gesture state regardless of
754
// whether we're actually dispatching the event or not.
755
if (mLastActiveApzc && mLastActiveApzc != target &&
756
mTouchCounter.GetActiveTouchCount() > 0) {
757
mLastActiveApzc->ResetTouchInputState();
758
}
759
if (curBlock->ShouldDropEvents()) {
760
if (curBlock->AsTouchBlock()) {
761
target->ResetTouchInputState();
762
}
763
} else {
764
UpdateActiveApzc(target);
765
curBlock->DispatchEvent(*(mQueuedInputs[0]->Input()));
766
}
767
}
768
mQueuedInputs.RemoveElementAt(0);
769
}
770
771
if (CanDiscardBlock(mActiveTouchBlock)) {
772
mActiveTouchBlock = nullptr;
773
}
774
if (CanDiscardBlock(mActiveWheelBlock)) {
775
mActiveWheelBlock = nullptr;
776
}
777
if (CanDiscardBlock(mActiveDragBlock)) {
778
mActiveDragBlock = nullptr;
779
}
780
if (CanDiscardBlock(mActivePanGestureBlock)) {
781
mActivePanGestureBlock = nullptr;
782
}
783
if (CanDiscardBlock(mActiveKeyboardBlock)) {
784
mActiveKeyboardBlock = nullptr;
785
}
786
}
787
788
bool InputQueue::CanDiscardBlock(InputBlockState* aBlock) {
789
if (!aBlock ||
790
(aBlock->AsCancelableBlock() &&
791
!aBlock->AsCancelableBlock()->IsReadyForHandling()) ||
792
aBlock->MustStayActive()) {
793
return false;
794
}
795
InputData* firstInput = nullptr;
796
FindBlockForId(aBlock->GetBlockId(), &firstInput);
797
if (firstInput) {
798
// The block has at least one input event still in the queue, so it's
799
// not depleted
800
return false;
801
}
802
return true;
803
}
804
805
void InputQueue::UpdateActiveApzc(
806
const RefPtr<AsyncPanZoomController>& aNewActive) {
807
mLastActiveApzc = aNewActive;
808
}
809
810
void InputQueue::Clear() {
811
APZThreadUtils::AssertOnControllerThread();
812
813
mQueuedInputs.Clear();
814
mActiveTouchBlock = nullptr;
815
mActiveWheelBlock = nullptr;
816
mActiveDragBlock = nullptr;
817
mActivePanGestureBlock = nullptr;
818
mActiveKeyboardBlock = nullptr;
819
mLastActiveApzc = nullptr;
820
}
821
822
InputQueue::AutoRunImmediateTimeout::AutoRunImmediateTimeout(InputQueue* aQueue)
823
: mQueue(aQueue) {
824
MOZ_ASSERT(!mQueue->mImmediateTimeout);
825
}
826
827
InputQueue::AutoRunImmediateTimeout::~AutoRunImmediateTimeout() {
828
if (mQueue->mImmediateTimeout) {
829
mQueue->mImmediateTimeout->Run();
830
mQueue->mImmediateTimeout = nullptr;
831
}
832
}
833
834
} // namespace layers
835
} // namespace mozilla