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 "InputBlockState.h"
8
9
#include "APZUtils.h"
10
#include "AsyncPanZoomController.h" // for AsyncPanZoomController
11
#include "ScrollAnimationPhysics.h" // for kScrollSeriesTimeoutMs
12
13
#include "mozilla/MouseEvents.h"
14
#include "mozilla/StaticPrefs_apz.h"
15
#include "mozilla/StaticPrefs_layout.h"
16
#include "mozilla/StaticPrefs_mousewheel.h"
17
#include "mozilla/StaticPrefs_test.h"
18
#include "mozilla/Telemetry.h" // for Telemetry
19
#include "mozilla/layers/IAPZCTreeManager.h" // for AllowedTouchBehavior
20
#include "LayersLogging.h" // for Stringify
21
#include "OverscrollHandoffState.h"
22
#include "QueuedInput.h"
23
24
static mozilla::LazyLogModule sApzIbsLog("apz.inputstate");
25
#define TBS_LOG(...) MOZ_LOG(sApzIbsLog, LogLevel::Debug, (__VA_ARGS__))
26
27
namespace mozilla {
28
namespace layers {
29
30
static uint64_t sBlockCounter = InputBlockState::NO_BLOCK_ID + 1;
31
32
InputBlockState::InputBlockState(
33
const RefPtr<AsyncPanZoomController>& aTargetApzc,
34
TargetConfirmationFlags aFlags)
35
: mTargetApzc(aTargetApzc),
36
mTargetConfirmed(aFlags.mTargetConfirmed
37
? TargetConfirmationState::eConfirmed
38
: TargetConfirmationState::eUnconfirmed),
39
mRequiresTargetConfirmation(aFlags.mRequiresTargetConfirmation),
40
mBlockId(sBlockCounter++),
41
mTransformToApzc(aTargetApzc->GetTransformToThis()) {
42
// We should never be constructed with a nullptr target.
43
MOZ_ASSERT(mTargetApzc);
44
mOverscrollHandoffChain = mTargetApzc->BuildOverscrollHandoffChain();
45
}
46
47
bool InputBlockState::SetConfirmedTargetApzc(
48
const RefPtr<AsyncPanZoomController>& aTargetApzc,
49
TargetConfirmationState aState, InputData* aFirstInput,
50
bool aForScrollbarDrag) {
51
MOZ_ASSERT(aState == TargetConfirmationState::eConfirmed ||
52
aState == TargetConfirmationState::eTimedOut);
53
54
if (mTargetConfirmed == TargetConfirmationState::eTimedOut &&
55
aState == TargetConfirmationState::eConfirmed) {
56
// The main thread finally responded. We had already timed out the
57
// confirmation, but we want to update the state internally so that we
58
// can record the time for telemetry purposes.
59
mTargetConfirmed = TargetConfirmationState::eTimedOutAndMainThreadResponded;
60
}
61
// Sometimes, bugs in compositor hit testing can lead to APZ confirming
62
// a different target than the main thread. If this happens for a drag
63
// block created for a scrollbar drag, the consequences can be fairly
64
// user-unfriendly, such as the scrollbar not being draggable at all,
65
// or it scrolling the contents of the wrong scrollframe. In debug
66
// builds, we assert in this situation, so that the
67
// underlying compositor hit testing bug can be fixed. In release builds,
68
// however, we just silently accept the main thread's confirmed target,
69
// which will produce the expected behaviour (apart from drag events
70
// received so far being dropped).
71
if (AsDragBlock() && aForScrollbarDrag &&
72
mTargetConfirmed == TargetConfirmationState::eConfirmed &&
73
aState == TargetConfirmationState::eConfirmed && mTargetApzc &&
74
aTargetApzc && mTargetApzc->GetGuid() != aTargetApzc->GetGuid()) {
75
MOZ_ASSERT(false,
76
"APZ and main thread confirmed scrollbar drag block with "
77
"different targets");
78
UpdateTargetApzc(aTargetApzc);
79
return true;
80
}
81
82
if (mTargetConfirmed != TargetConfirmationState::eUnconfirmed) {
83
return false;
84
}
85
mTargetConfirmed = aState;
86
87
TBS_LOG("%p got confirmed target APZC %p\n", this, mTargetApzc.get());
88
if (mTargetApzc == aTargetApzc) {
89
// The confirmed target is the same as the tentative one, so we're done.
90
return true;
91
}
92
93
TBS_LOG("%p replacing unconfirmed target %p with real target %p\n", this,
94
mTargetApzc.get(), aTargetApzc.get());
95
96
UpdateTargetApzc(aTargetApzc);
97
return true;
98
}
99
100
void InputBlockState::UpdateTargetApzc(
101
const RefPtr<AsyncPanZoomController>& aTargetApzc) {
102
// note that aTargetApzc MAY be null here.
103
mTargetApzc = aTargetApzc;
104
mTransformToApzc = aTargetApzc ? aTargetApzc->GetTransformToThis()
105
: ScreenToParentLayerMatrix4x4();
106
mOverscrollHandoffChain =
107
(mTargetApzc ? mTargetApzc->BuildOverscrollHandoffChain() : nullptr);
108
}
109
110
const RefPtr<AsyncPanZoomController>& InputBlockState::GetTargetApzc() const {
111
return mTargetApzc;
112
}
113
114
const RefPtr<const OverscrollHandoffChain>&
115
InputBlockState::GetOverscrollHandoffChain() const {
116
return mOverscrollHandoffChain;
117
}
118
119
uint64_t InputBlockState::GetBlockId() const { return mBlockId; }
120
121
bool InputBlockState::IsTargetConfirmed() const {
122
return mTargetConfirmed != TargetConfirmationState::eUnconfirmed;
123
}
124
125
bool InputBlockState::HasReceivedRealConfirmedTarget() const {
126
return mTargetConfirmed == TargetConfirmationState::eConfirmed ||
127
mTargetConfirmed ==
128
TargetConfirmationState::eTimedOutAndMainThreadResponded;
129
}
130
131
bool InputBlockState::ShouldDropEvents() const {
132
return mRequiresTargetConfirmation &&
133
(mTargetConfirmed != TargetConfirmationState::eConfirmed);
134
}
135
136
bool InputBlockState::IsDownchainOf(AsyncPanZoomController* aA,
137
AsyncPanZoomController* aB) const {
138
if (aA == aB) {
139
return true;
140
}
141
142
bool seenA = false;
143
for (size_t i = 0; i < mOverscrollHandoffChain->Length(); ++i) {
144
AsyncPanZoomController* apzc = mOverscrollHandoffChain->GetApzcAtIndex(i);
145
if (apzc == aB) {
146
return seenA;
147
}
148
if (apzc == aA) {
149
seenA = true;
150
}
151
}
152
return false;
153
}
154
155
void InputBlockState::SetScrolledApzc(AsyncPanZoomController* aApzc) {
156
// An input block should only have one scrolled APZC.
157
MOZ_ASSERT(!mScrolledApzc || (StaticPrefs::apz_allow_immediate_handoff()
158
? IsDownchainOf(mScrolledApzc, aApzc)
159
: mScrolledApzc == aApzc));
160
161
mScrolledApzc = aApzc;
162
}
163
164
AsyncPanZoomController* InputBlockState::GetScrolledApzc() const {
165
return mScrolledApzc;
166
}
167
168
bool InputBlockState::IsDownchainOfScrolledApzc(
169
AsyncPanZoomController* aApzc) const {
170
MOZ_ASSERT(aApzc && mScrolledApzc);
171
172
return IsDownchainOf(mScrolledApzc, aApzc);
173
}
174
175
void InputBlockState::DispatchEvent(const InputData& aEvent) const {
176
GetTargetApzc()->HandleInputEvent(aEvent, mTransformToApzc);
177
}
178
179
CancelableBlockState::CancelableBlockState(
180
const RefPtr<AsyncPanZoomController>& aTargetApzc,
181
TargetConfirmationFlags aFlags)
182
: InputBlockState(aTargetApzc, aFlags),
183
mPreventDefault(false),
184
mContentResponded(false),
185
mContentResponseTimerExpired(false) {}
186
187
bool CancelableBlockState::SetContentResponse(bool aPreventDefault) {
188
if (mContentResponded) {
189
return false;
190
}
191
TBS_LOG("%p got content response %d with timer expired %d\n", this,
192
aPreventDefault, mContentResponseTimerExpired);
193
mPreventDefault = aPreventDefault;
194
mContentResponded = true;
195
return true;
196
}
197
198
void CancelableBlockState::StartContentResponseTimer() {
199
MOZ_ASSERT(mContentResponseTimer.IsNull());
200
mContentResponseTimer = TimeStamp::Now();
201
}
202
203
bool CancelableBlockState::TimeoutContentResponse() {
204
if (mContentResponseTimerExpired) {
205
return false;
206
}
207
TBS_LOG("%p got content timer expired with response received %d\n", this,
208
mContentResponded);
209
if (!mContentResponded) {
210
mPreventDefault = false;
211
}
212
mContentResponseTimerExpired = true;
213
return true;
214
}
215
216
bool CancelableBlockState::IsContentResponseTimerExpired() const {
217
return mContentResponseTimerExpired;
218
}
219
220
bool CancelableBlockState::IsDefaultPrevented() const {
221
MOZ_ASSERT(mContentResponded || mContentResponseTimerExpired);
222
return mPreventDefault;
223
}
224
225
bool CancelableBlockState::HasReceivedAllContentNotifications() const {
226
return HasReceivedRealConfirmedTarget() && mContentResponded;
227
}
228
229
bool CancelableBlockState::IsReadyForHandling() const {
230
if (!IsTargetConfirmed()) {
231
return false;
232
}
233
return mContentResponded || mContentResponseTimerExpired;
234
}
235
236
bool CancelableBlockState::ShouldDropEvents() const {
237
return InputBlockState::ShouldDropEvents() || IsDefaultPrevented();
238
}
239
240
void CancelableBlockState::RecordContentResponseTime() {
241
if (!mContentResponseTimer) {
242
// We might get responses from content even though we didn't wait for them.
243
// In that case, ignore the time on them, because they're not relevant for
244
// tuning our timeout value. Also this function might get called multiple
245
// times on the same input block, so we should only record the time from the
246
// first successful call.
247
return;
248
}
249
if (!HasReceivedAllContentNotifications()) {
250
// Not done yet, we'll get called again
251
return;
252
}
253
mozilla::Telemetry::Accumulate(
254
mozilla::Telemetry::CONTENT_RESPONSE_DURATION,
255
(uint32_t)(TimeStamp::Now() - mContentResponseTimer).ToMilliseconds());
256
mContentResponseTimer = TimeStamp();
257
}
258
259
DragBlockState::DragBlockState(
260
const RefPtr<AsyncPanZoomController>& aTargetApzc,
261
TargetConfirmationFlags aFlags, const MouseInput& aInitialEvent)
262
: CancelableBlockState(aTargetApzc, aFlags), mReceivedMouseUp(false) {}
263
264
bool DragBlockState::HasReceivedMouseUp() { return mReceivedMouseUp; }
265
266
void DragBlockState::MarkMouseUpReceived() { mReceivedMouseUp = true; }
267
268
void DragBlockState::SetInitialThumbPos(CSSCoord aThumbPos) {
269
mInitialThumbPos = aThumbPos;
270
}
271
272
void DragBlockState::SetDragMetrics(const AsyncDragMetrics& aDragMetrics) {
273
mDragMetrics = aDragMetrics;
274
}
275
276
void DragBlockState::DispatchEvent(const InputData& aEvent) const {
277
MouseInput mouseInput = aEvent.AsMouseInput();
278
if (!mouseInput.TransformToLocal(mTransformToApzc)) {
279
return;
280
}
281
282
GetTargetApzc()->HandleDragEvent(mouseInput, mDragMetrics, mInitialThumbPos);
283
}
284
285
bool DragBlockState::MustStayActive() { return !mReceivedMouseUp; }
286
287
const char* DragBlockState::Type() { return "drag"; }
288
// This is used to track the current wheel transaction.
289
static uint64_t sLastWheelBlockId = InputBlockState::NO_BLOCK_ID;
290
291
WheelBlockState::WheelBlockState(
292
const RefPtr<AsyncPanZoomController>& aTargetApzc,
293
TargetConfirmationFlags aFlags, const ScrollWheelInput& aInitialEvent)
294
: CancelableBlockState(aTargetApzc, aFlags),
295
mScrollSeriesCounter(0),
296
mTransactionEnded(false) {
297
sLastWheelBlockId = GetBlockId();
298
299
if (aFlags.mTargetConfirmed) {
300
// Find the nearest APZC in the overscroll handoff chain that is scrollable.
301
// If we get a content confirmation later that the apzc is different, then
302
// content should have found a scrollable apzc, so we don't need to handle
303
// that case.
304
RefPtr<AsyncPanZoomController> apzc =
305
mOverscrollHandoffChain->FindFirstScrollable(aInitialEvent,
306
&mAllowedScrollDirections);
307
308
// If nothing is scrollable, we don't consider this block as starting a
309
// transaction.
310
if (!apzc) {
311
EndTransaction();
312
return;
313
}
314
315
if (apzc != GetTargetApzc()) {
316
UpdateTargetApzc(apzc);
317
}
318
}
319
}
320
321
bool WheelBlockState::SetContentResponse(bool aPreventDefault) {
322
if (aPreventDefault) {
323
EndTransaction();
324
}
325
return CancelableBlockState::SetContentResponse(aPreventDefault);
326
}
327
328
bool WheelBlockState::SetConfirmedTargetApzc(
329
const RefPtr<AsyncPanZoomController>& aTargetApzc,
330
TargetConfirmationState aState, InputData* aFirstInput,
331
bool aForScrollbarDrag) {
332
// The APZC that we find via APZCCallbackHelpers may not be the same APZC
333
// ESM or OverscrollHandoff would have computed. Make sure we get the right
334
// one by looking for the first apzc the next pending event can scroll.
335
RefPtr<AsyncPanZoomController> apzc = aTargetApzc;
336
if (apzc && aFirstInput) {
337
apzc = apzc->BuildOverscrollHandoffChain()->FindFirstScrollable(
338
*aFirstInput, &mAllowedScrollDirections);
339
}
340
341
InputBlockState::SetConfirmedTargetApzc(apzc, aState, aFirstInput,
342
aForScrollbarDrag);
343
return true;
344
}
345
346
void WheelBlockState::Update(ScrollWheelInput& aEvent) {
347
// We might not be in a transaction if the block never started in a
348
// transaction - for example, if nothing was scrollable.
349
if (!InTransaction()) {
350
return;
351
}
352
353
// The current "scroll series" is a like a sub-transaction. It has a separate
354
// timeout of 80ms. Since we need to compute wheel deltas at different phases
355
// of a transaction (for example, when it is updated, and later when the
356
// event action is taken), we affix the scroll series counter to the event.
357
// This makes GetScrollWheelDelta() consistent.
358
if (!mLastEventTime.IsNull() &&
359
(aEvent.mTimeStamp - mLastEventTime).ToMilliseconds() >
360
kScrollSeriesTimeoutMs) {
361
mScrollSeriesCounter = 0;
362
}
363
aEvent.mScrollSeriesNumber = ++mScrollSeriesCounter;
364
365
// If we can't scroll in the direction of the wheel event, we don't update
366
// the last move time. This allows us to timeout a transaction even if the
367
// mouse isn't moving.
368
//
369
// We skip this check if the target is not yet confirmed, so that when it is
370
// confirmed, we don't timeout the transaction.
371
RefPtr<AsyncPanZoomController> apzc = GetTargetApzc();
372
if (IsTargetConfirmed() && !apzc->CanScroll(aEvent)) {
373
return;
374
}
375
376
// Update the time of the last known good event, and reset the mouse move
377
// time to null. This will reset the delays on both the general transaction
378
// timeout and the mouse-move-in-frame timeout.
379
mLastEventTime = aEvent.mTimeStamp;
380
mLastMouseMove = TimeStamp();
381
}
382
383
bool WheelBlockState::MustStayActive() { return !mTransactionEnded; }
384
385
const char* WheelBlockState::Type() { return "scroll wheel"; }
386
387
bool WheelBlockState::ShouldAcceptNewEvent() const {
388
if (!InTransaction()) {
389
// If we're not in a transaction, start a new one.
390
return false;
391
}
392
393
RefPtr<AsyncPanZoomController> apzc = GetTargetApzc();
394
if (apzc->IsDestroyed()) {
395
return false;
396
}
397
398
return true;
399
}
400
401
bool WheelBlockState::MaybeTimeout(const ScrollWheelInput& aEvent) {
402
MOZ_ASSERT(InTransaction());
403
404
if (MaybeTimeout(aEvent.mTimeStamp)) {
405
return true;
406
}
407
408
if (!mLastMouseMove.IsNull()) {
409
// If there's a recent mouse movement, we can time out the transaction
410
// early.
411
TimeDuration duration = TimeStamp::Now() - mLastMouseMove;
412
if (duration.ToMilliseconds() >=
413
StaticPrefs::mousewheel_transaction_ignoremovedelay()) {
414
TBS_LOG("%p wheel transaction timed out after mouse move\n", this);
415
EndTransaction();
416
return true;
417
}
418
}
419
420
return false;
421
}
422
423
bool WheelBlockState::MaybeTimeout(const TimeStamp& aTimeStamp) {
424
MOZ_ASSERT(InTransaction());
425
426
// End the transaction if the event occurred > 1.5s after the most recently
427
// seen wheel event.
428
TimeDuration duration = aTimeStamp - mLastEventTime;
429
if (duration.ToMilliseconds() <
430
StaticPrefs::mousewheel_transaction_timeout()) {
431
return false;
432
}
433
434
TBS_LOG("%p wheel transaction timed out\n", this);
435
436
if (StaticPrefs::test_mousescroll()) {
437
RefPtr<AsyncPanZoomController> apzc = GetTargetApzc();
438
apzc->NotifyMozMouseScrollEvent(
439
NS_LITERAL_STRING("MozMouseScrollTransactionTimeout"));
440
}
441
442
EndTransaction();
443
return true;
444
}
445
446
void WheelBlockState::OnMouseMove(const ScreenIntPoint& aPoint) {
447
MOZ_ASSERT(InTransaction());
448
449
if (!GetTargetApzc()->Contains(aPoint)) {
450
EndTransaction();
451
return;
452
}
453
454
if (mLastMouseMove.IsNull()) {
455
// If the cursor is moving inside the frame, and it is more than the
456
// ignoremovedelay time since the last scroll operation, we record
457
// this as the most recent mouse movement.
458
TimeStamp now = TimeStamp::Now();
459
TimeDuration duration = now - mLastEventTime;
460
if (duration.ToMilliseconds() >=
461
StaticPrefs::mousewheel_transaction_ignoremovedelay()) {
462
mLastMouseMove = now;
463
}
464
}
465
}
466
467
void WheelBlockState::UpdateTargetApzc(
468
const RefPtr<AsyncPanZoomController>& aTargetApzc) {
469
InputBlockState::UpdateTargetApzc(aTargetApzc);
470
471
// If we found there was no target apzc, then we end the transaction.
472
if (!GetTargetApzc()) {
473
EndTransaction();
474
}
475
}
476
477
bool WheelBlockState::InTransaction() const {
478
// We consider a wheel block to be in a transaction if it has a confirmed
479
// target and is the most recent wheel input block to be created.
480
if (GetBlockId() != sLastWheelBlockId) {
481
return false;
482
}
483
484
if (mTransactionEnded) {
485
return false;
486
}
487
488
MOZ_ASSERT(GetTargetApzc());
489
return true;
490
}
491
492
bool WheelBlockState::AllowScrollHandoff() const {
493
// If we're in a wheel transaction, we do not allow overscroll handoff until
494
// a new event ends the wheel transaction.
495
return !IsTargetConfirmed() || !InTransaction();
496
}
497
498
void WheelBlockState::EndTransaction() {
499
TBS_LOG("%p ending wheel transaction\n", this);
500
mTransactionEnded = true;
501
}
502
503
PanGestureBlockState::PanGestureBlockState(
504
const RefPtr<AsyncPanZoomController>& aTargetApzc,
505
TargetConfirmationFlags aFlags, const PanGestureInput& aInitialEvent)
506
: CancelableBlockState(aTargetApzc, aFlags),
507
mInterrupted(false),
508
mWaitingForContentResponse(false) {
509
if (aFlags.mTargetConfirmed) {
510
// Find the nearest APZC in the overscroll handoff chain that is scrollable.
511
// If we get a content confirmation later that the apzc is different, then
512
// content should have found a scrollable apzc, so we don't need to handle
513
// that case.
514
RefPtr<AsyncPanZoomController> apzc =
515
mOverscrollHandoffChain->FindFirstScrollable(aInitialEvent,
516
&mAllowedScrollDirections);
517
518
if (apzc && apzc != GetTargetApzc()) {
519
UpdateTargetApzc(apzc);
520
}
521
}
522
}
523
524
bool PanGestureBlockState::SetConfirmedTargetApzc(
525
const RefPtr<AsyncPanZoomController>& aTargetApzc,
526
TargetConfirmationState aState, InputData* aFirstInput,
527
bool aForScrollbarDrag) {
528
// The APZC that we find via APZCCallbackHelpers may not be the same APZC
529
// ESM or OverscrollHandoff would have computed. Make sure we get the right
530
// one by looking for the first apzc the next pending event can scroll.
531
RefPtr<AsyncPanZoomController> apzc = aTargetApzc;
532
if (apzc && aFirstInput) {
533
RefPtr<AsyncPanZoomController> scrollableApzc =
534
apzc->BuildOverscrollHandoffChain()->FindFirstScrollable(
535
*aFirstInput, &mAllowedScrollDirections);
536
if (scrollableApzc) {
537
apzc = scrollableApzc;
538
}
539
}
540
541
InputBlockState::SetConfirmedTargetApzc(apzc, aState, aFirstInput,
542
aForScrollbarDrag);
543
return true;
544
}
545
546
bool PanGestureBlockState::MustStayActive() { return !mInterrupted; }
547
548
const char* PanGestureBlockState::Type() { return "pan gesture"; }
549
550
bool PanGestureBlockState::SetContentResponse(bool aPreventDefault) {
551
if (aPreventDefault) {
552
TBS_LOG("%p setting interrupted flag\n", this);
553
mInterrupted = true;
554
}
555
bool stateChanged = CancelableBlockState::SetContentResponse(aPreventDefault);
556
if (mWaitingForContentResponse) {
557
mWaitingForContentResponse = false;
558
stateChanged = true;
559
}
560
return stateChanged;
561
}
562
563
bool PanGestureBlockState::HasReceivedAllContentNotifications() const {
564
return CancelableBlockState::HasReceivedAllContentNotifications() &&
565
!mWaitingForContentResponse;
566
}
567
568
bool PanGestureBlockState::IsReadyForHandling() const {
569
if (!CancelableBlockState::IsReadyForHandling()) {
570
return false;
571
}
572
return !mWaitingForContentResponse || IsContentResponseTimerExpired();
573
}
574
575
bool PanGestureBlockState::AllowScrollHandoff() const { return false; }
576
577
void PanGestureBlockState::SetNeedsToWaitForContentResponse(
578
bool aWaitForContentResponse) {
579
mWaitingForContentResponse = aWaitForContentResponse;
580
}
581
582
TouchBlockState::TouchBlockState(
583
const RefPtr<AsyncPanZoomController>& aTargetApzc,
584
TargetConfirmationFlags aFlags, TouchCounter& aCounter)
585
: CancelableBlockState(aTargetApzc, aFlags),
586
mAllowedTouchBehaviorSet(false),
587
mDuringFastFling(false),
588
mSingleTapOccurred(false),
589
mInSlop(false),
590
mTouchCounter(aCounter) {
591
TBS_LOG("Creating %p\n", this);
592
if (!StaticPrefs::layout_css_touch_action_enabled()) {
593
mAllowedTouchBehaviorSet = true;
594
}
595
}
596
597
bool TouchBlockState::SetAllowedTouchBehaviors(
598
const nsTArray<TouchBehaviorFlags>& aBehaviors) {
599
if (mAllowedTouchBehaviorSet) {
600
return false;
601
}
602
TBS_LOG("%p got allowed touch behaviours for %zu points\n", this,
603
aBehaviors.Length());
604
mAllowedTouchBehaviors.AppendElements(aBehaviors);
605
mAllowedTouchBehaviorSet = true;
606
return true;
607
}
608
609
bool TouchBlockState::GetAllowedTouchBehaviors(
610
nsTArray<TouchBehaviorFlags>& aOutBehaviors) const {
611
if (!mAllowedTouchBehaviorSet) {
612
return false;
613
}
614
aOutBehaviors.AppendElements(mAllowedTouchBehaviors);
615
return true;
616
}
617
618
bool TouchBlockState::HasAllowedTouchBehaviors() const {
619
return mAllowedTouchBehaviorSet;
620
}
621
622
void TouchBlockState::CopyPropertiesFrom(const TouchBlockState& aOther) {
623
TBS_LOG("%p copying properties from %p\n", this, &aOther);
624
if (StaticPrefs::layout_css_touch_action_enabled()) {
625
MOZ_ASSERT(aOther.mAllowedTouchBehaviorSet ||
626
aOther.IsContentResponseTimerExpired());
627
SetAllowedTouchBehaviors(aOther.mAllowedTouchBehaviors);
628
}
629
mTransformToApzc = aOther.mTransformToApzc;
630
}
631
632
bool TouchBlockState::HasReceivedAllContentNotifications() const {
633
return CancelableBlockState::HasReceivedAllContentNotifications()
634
// See comment in TouchBlockState::IsReadyforHandling()
635
&& (!StaticPrefs::layout_css_touch_action_enabled() ||
636
mAllowedTouchBehaviorSet);
637
}
638
639
bool TouchBlockState::IsReadyForHandling() const {
640
if (!CancelableBlockState::IsReadyForHandling()) {
641
return false;
642
}
643
644
if (!StaticPrefs::layout_css_touch_action_enabled()) {
645
// If layout_css_touch_action_enabled() was false when this block was
646
// created, then mAllowedTouchBehaviorSet is guaranteed to the true.
647
// However, the pref may have been flipped to false after the block was
648
// created. In that case, we should eventually get the touch-behaviour
649
// notification, or expire the content response timeout, but we don't really
650
// need to wait for those, since we don't care about the touch-behaviour
651
// values any more.
652
return true;
653
}
654
655
return mAllowedTouchBehaviorSet || IsContentResponseTimerExpired();
656
}
657
658
void TouchBlockState::SetDuringFastFling() {
659
TBS_LOG("%p setting fast-motion flag\n", this);
660
mDuringFastFling = true;
661
}
662
663
bool TouchBlockState::IsDuringFastFling() const { return mDuringFastFling; }
664
665
void TouchBlockState::SetSingleTapOccurred() {
666
TBS_LOG("%p setting single-tap-occurred flag\n", this);
667
mSingleTapOccurred = true;
668
}
669
670
bool TouchBlockState::SingleTapOccurred() const { return mSingleTapOccurred; }
671
672
bool TouchBlockState::MustStayActive() { return true; }
673
674
const char* TouchBlockState::Type() { return "touch"; }
675
676
void TouchBlockState::DispatchEvent(const InputData& aEvent) const {
677
MOZ_ASSERT(aEvent.mInputType == MULTITOUCH_INPUT);
678
mTouchCounter.Update(aEvent.AsMultiTouchInput());
679
CancelableBlockState::DispatchEvent(aEvent);
680
}
681
682
bool TouchBlockState::TouchActionAllowsPinchZoom() const {
683
if (!StaticPrefs::layout_css_touch_action_enabled()) {
684
return true;
685
}
686
// Pointer events specification requires that all touch points allow zoom.
687
for (size_t i = 0; i < mAllowedTouchBehaviors.Length(); i++) {
688
if (!(mAllowedTouchBehaviors[i] & AllowedTouchBehavior::PINCH_ZOOM)) {
689
return false;
690
}
691
}
692
return true;
693
}
694
695
bool TouchBlockState::TouchActionAllowsDoubleTapZoom() const {
696
if (!StaticPrefs::layout_css_touch_action_enabled()) {
697
return true;
698
}
699
for (size_t i = 0; i < mAllowedTouchBehaviors.Length(); i++) {
700
if (!(mAllowedTouchBehaviors[i] & AllowedTouchBehavior::DOUBLE_TAP_ZOOM)) {
701
return false;
702
}
703
}
704
return true;
705
}
706
707
bool TouchBlockState::TouchActionAllowsPanningX() const {
708
if (!StaticPrefs::layout_css_touch_action_enabled()) {
709
return true;
710
}
711
if (mAllowedTouchBehaviors.IsEmpty()) {
712
// Default to allowed
713
return true;
714
}
715
TouchBehaviorFlags flags = mAllowedTouchBehaviors[0];
716
return (flags & AllowedTouchBehavior::HORIZONTAL_PAN);
717
}
718
719
bool TouchBlockState::TouchActionAllowsPanningY() const {
720
if (!StaticPrefs::layout_css_touch_action_enabled()) {
721
return true;
722
}
723
if (mAllowedTouchBehaviors.IsEmpty()) {
724
// Default to allowed
725
return true;
726
}
727
TouchBehaviorFlags flags = mAllowedTouchBehaviors[0];
728
return (flags & AllowedTouchBehavior::VERTICAL_PAN);
729
}
730
731
bool TouchBlockState::TouchActionAllowsPanningXY() const {
732
if (!StaticPrefs::layout_css_touch_action_enabled()) {
733
return true;
734
}
735
if (mAllowedTouchBehaviors.IsEmpty()) {
736
// Default to allowed
737
return true;
738
}
739
TouchBehaviorFlags flags = mAllowedTouchBehaviors[0];
740
return (flags & AllowedTouchBehavior::HORIZONTAL_PAN) &&
741
(flags & AllowedTouchBehavior::VERTICAL_PAN);
742
}
743
744
bool TouchBlockState::UpdateSlopState(const MultiTouchInput& aInput,
745
bool aApzcCanConsumeEvents) {
746
if (aInput.mType == MultiTouchInput::MULTITOUCH_START) {
747
// this is by definition the first event in this block. If it's the first
748
// touch, then we enter a slop state.
749
mInSlop = (aInput.mTouches.Length() == 1);
750
if (mInSlop) {
751
mSlopOrigin = aInput.mTouches[0].mScreenPoint;
752
TBS_LOG("%p entering slop with origin %s\n", this,
753
Stringify(mSlopOrigin).c_str());
754
}
755
return false;
756
}
757
if (mInSlop) {
758
ScreenCoord threshold = 0;
759
// If the target was confirmed to null then the threshold doesn't
760
// matter anyway since the events will never be processed.
761
if (const RefPtr<AsyncPanZoomController>& apzc = GetTargetApzc()) {
762
threshold = aApzcCanConsumeEvents ? apzc->GetTouchStartTolerance()
763
: apzc->GetTouchMoveTolerance();
764
}
765
bool stayInSlop =
766
(aInput.mType == MultiTouchInput::MULTITOUCH_MOVE) &&
767
(aInput.mTouches.Length() == 1) &&
768
((aInput.mTouches[0].mScreenPoint - mSlopOrigin).Length() < threshold);
769
if (!stayInSlop) {
770
// we're out of the slop zone, and will stay out for the remainder of
771
// this block
772
TBS_LOG("%p exiting slop\n", this);
773
mInSlop = false;
774
}
775
}
776
return mInSlop;
777
}
778
779
Maybe<ScrollDirection> TouchBlockState::GetBestGuessPanDirection(
780
const MultiTouchInput& aInput) {
781
if (aInput.mType != MultiTouchInput::MULTITOUCH_MOVE ||
782
aInput.mTouches.Length() != 1) {
783
return Nothing();
784
}
785
ScreenPoint vector = aInput.mTouches[0].mScreenPoint - mSlopOrigin;
786
double angle = atan2(vector.y, vector.x); // range [-pi, pi]
787
angle = fabs(angle); // range [0, pi]
788
789
double angleThreshold = TouchActionAllowsPanningXY()
790
? StaticPrefs::apz_axis_lock_lock_angle()
791
: StaticPrefs::apz_axis_lock_direct_pan_angle();
792
if (apz::IsCloseToHorizontal(angle, angleThreshold)) {
793
return Some(ScrollDirection::eHorizontal);
794
}
795
if (apz::IsCloseToVertical(angle, angleThreshold)) {
796
return Some(ScrollDirection::eVertical);
797
}
798
return Nothing();
799
}
800
801
uint32_t TouchBlockState::GetActiveTouchCount() const {
802
return mTouchCounter.GetActiveTouchCount();
803
}
804
805
KeyboardBlockState::KeyboardBlockState(
806
const RefPtr<AsyncPanZoomController>& aTargetApzc)
807
: InputBlockState(aTargetApzc, TargetConfirmationFlags{true}) {}
808
809
} // namespace layers
810
} // namespace mozilla