Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/gfx/layers/apz/util/APZEventState.cpp
Line
Count
Source (jump to first uncovered line)
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 "APZEventState.h"
8
9
#include "ActiveElementManager.h"
10
#include "APZCCallbackHelper.h"
11
#include "gfxPrefs.h"
12
#include "LayersLogging.h"
13
#include "mozilla/BasicEvents.h"
14
#include "mozilla/dom/MouseEventBinding.h"
15
#include "mozilla/dom/TabChild.h"
16
#include "mozilla/dom/TabGroup.h"
17
#include "mozilla/IntegerPrintfMacros.h"
18
#include "mozilla/Move.h"
19
#include "mozilla/Preferences.h"
20
#include "mozilla/TouchEvents.h"
21
#include "mozilla/layers/APZCCallbackHelper.h"
22
#include "nsCOMPtr.h"
23
#include "nsDocShell.h"
24
#include "nsIDOMWindowUtils.h"
25
#include "nsINamed.h"
26
#include "nsIScrollableFrame.h"
27
#include "nsIScrollbarMediator.h"
28
#include "nsITimer.h"
29
#include "nsIWeakReferenceUtils.h"
30
#include "nsIWidget.h"
31
#include "nsLayoutUtils.h"
32
#include "nsQueryFrame.h"
33
#include "TouchManager.h"
34
#include "nsLayoutUtils.h"
35
#include "nsIScrollableFrame.h"
36
#include "nsIScrollbarMediator.h"
37
#include "mozilla/TouchEvents.h"
38
#include "mozilla/widget/nsAutoRollup.h"
39
40
#define APZES_LOG(...)
41
// #define APZES_LOG(...) printf_stderr("APZES: " __VA_ARGS__)
42
43
// Static helper functions
44
namespace {
45
46
int32_t
47
WidgetModifiersToDOMModifiers(mozilla::Modifiers aModifiers)
48
0
{
49
0
  int32_t result = 0;
50
0
  if (aModifiers & mozilla::MODIFIER_SHIFT) {
51
0
    result |= nsIDOMWindowUtils::MODIFIER_SHIFT;
52
0
  }
53
0
  if (aModifiers & mozilla::MODIFIER_CONTROL) {
54
0
    result |= nsIDOMWindowUtils::MODIFIER_CONTROL;
55
0
  }
56
0
  if (aModifiers & mozilla::MODIFIER_ALT) {
57
0
    result |= nsIDOMWindowUtils::MODIFIER_ALT;
58
0
  }
59
0
  if (aModifiers & mozilla::MODIFIER_META) {
60
0
    result |= nsIDOMWindowUtils::MODIFIER_META;
61
0
  }
62
0
  if (aModifiers & mozilla::MODIFIER_ALTGRAPH) {
63
0
    result |= nsIDOMWindowUtils::MODIFIER_ALTGRAPH;
64
0
  }
65
0
  if (aModifiers & mozilla::MODIFIER_CAPSLOCK) {
66
0
    result |= nsIDOMWindowUtils::MODIFIER_CAPSLOCK;
67
0
  }
68
0
  if (aModifiers & mozilla::MODIFIER_FN) {
69
0
    result |= nsIDOMWindowUtils::MODIFIER_FN;
70
0
  }
71
0
  if (aModifiers & mozilla::MODIFIER_FNLOCK) {
72
0
    result |= nsIDOMWindowUtils::MODIFIER_FNLOCK;
73
0
  }
74
0
  if (aModifiers & mozilla::MODIFIER_NUMLOCK) {
75
0
    result |= nsIDOMWindowUtils::MODIFIER_NUMLOCK;
76
0
  }
77
0
  if (aModifiers & mozilla::MODIFIER_SCROLLLOCK) {
78
0
    result |= nsIDOMWindowUtils::MODIFIER_SCROLLLOCK;
79
0
  }
80
0
  if (aModifiers & mozilla::MODIFIER_SYMBOL) {
81
0
    result |= nsIDOMWindowUtils::MODIFIER_SYMBOL;
82
0
  }
83
0
  if (aModifiers & mozilla::MODIFIER_SYMBOLLOCK) {
84
0
    result |= nsIDOMWindowUtils::MODIFIER_SYMBOLLOCK;
85
0
  }
86
0
  if (aModifiers & mozilla::MODIFIER_OS) {
87
0
    result |= nsIDOMWindowUtils::MODIFIER_OS;
88
0
  }
89
0
  return result;
90
0
}
91
92
} // namespace
93
94
namespace mozilla {
95
namespace layers {
96
97
static int32_t sActiveDurationMs = 10;
98
static bool sActiveDurationMsSet = false;
99
100
APZEventState::APZEventState(nsIWidget* aWidget,
101
                             ContentReceivedInputBlockCallback&& aCallback)
102
  : mWidget(nullptr)  // initialized in constructor body
103
  , mActiveElementManager(new ActiveElementManager())
104
  , mContentReceivedInputBlockCallback(std::move(aCallback))
105
  , mPendingTouchPreventedResponse(false)
106
  , mPendingTouchPreventedBlockId(0)
107
  , mEndTouchIsClick(false)
108
  , mTouchEndCancelled(false)
109
  , mLastTouchIdentifier(0)
110
0
{
111
0
  nsresult rv;
112
0
  mWidget = do_GetWeakReference(aWidget, &rv);
113
0
  MOZ_ASSERT(NS_SUCCEEDED(rv), "APZEventState constructed with a widget that"
114
0
      " does not support weak references. APZ will NOT work!");
115
0
116
0
  if (!sActiveDurationMsSet) {
117
0
    Preferences::AddIntVarCache(&sActiveDurationMs,
118
0
                                "ui.touch_activation.duration_ms",
119
0
                                sActiveDurationMs);
120
0
    sActiveDurationMsSet = true;
121
0
  }
122
0
}
123
124
APZEventState::~APZEventState()
125
0
{}
126
127
class DelayedFireSingleTapEvent final : public nsITimerCallback
128
                                      , public nsINamed
129
{
130
public:
131
  NS_DECL_ISUPPORTS
132
133
  DelayedFireSingleTapEvent(nsWeakPtr aWidget,
134
                            LayoutDevicePoint& aPoint,
135
                            Modifiers aModifiers,
136
                            int32_t aClickCount,
137
                            nsITimer* aTimer,
138
                            RefPtr<nsIContent>& aTouchRollup)
139
    : mWidget(aWidget)
140
    , mPoint(aPoint)
141
    , mModifiers(aModifiers)
142
    , mClickCount(aClickCount)
143
    // Hold the reference count until we are called back.
144
    , mTimer(aTimer)
145
    , mTouchRollup(aTouchRollup)
146
0
  {
147
0
  }
148
149
  NS_IMETHOD Notify(nsITimer*) override
150
0
  {
151
0
    if (nsCOMPtr<nsIWidget> widget = do_QueryReferent(mWidget)) {
152
0
      widget::nsAutoRollup rollup(mTouchRollup.get());
153
0
      APZCCallbackHelper::FireSingleTapEvent(mPoint, mModifiers, mClickCount, widget);
154
0
    }
155
0
    mTimer = nullptr;
156
0
    return NS_OK;
157
0
  }
158
159
  NS_IMETHOD
160
  GetName(nsACString& aName) override
161
0
  {
162
0
    aName.AssignLiteral("DelayedFireSingleTapEvent");
163
0
    return NS_OK;
164
0
  }
165
166
0
  void ClearTimer() {
167
0
    mTimer = nullptr;
168
0
  }
169
170
private:
171
  ~DelayedFireSingleTapEvent()
172
0
  {
173
0
  }
174
175
  nsWeakPtr mWidget;
176
  LayoutDevicePoint mPoint;
177
  Modifiers mModifiers;
178
  int32_t mClickCount;
179
  nsCOMPtr<nsITimer> mTimer;
180
  RefPtr<nsIContent> mTouchRollup;
181
};
182
183
NS_IMPL_ISUPPORTS(DelayedFireSingleTapEvent, nsITimerCallback, nsINamed)
184
185
void
186
APZEventState::ProcessSingleTap(const CSSPoint& aPoint,
187
                                const CSSToLayoutDeviceScale& aScale,
188
                                Modifiers aModifiers,
189
                                const ScrollableLayerGuid& aGuid,
190
                                int32_t aClickCount)
191
0
{
192
0
  APZES_LOG("Handling single tap at %s on %s with %d\n",
193
0
    Stringify(aPoint).c_str(), Stringify(aGuid).c_str(), mTouchEndCancelled);
194
0
195
0
  RefPtr<nsIContent> touchRollup = GetTouchRollup();
196
0
  mTouchRollup = nullptr;
197
0
198
0
  nsCOMPtr<nsIWidget> widget = GetWidget();
199
0
  if (!widget) {
200
0
    return;
201
0
  }
202
0
203
0
  if (mTouchEndCancelled) {
204
0
    return;
205
0
  }
206
0
207
0
  LayoutDevicePoint ldPoint = aPoint * aScale;
208
0
209
0
  APZES_LOG("Scheduling timer for click event\n");
210
0
  nsCOMPtr<nsITimer> timer = NS_NewTimer();
211
0
  dom::TabChild* tabChild = widget->GetOwningTabChild();
212
0
213
0
  if (tabChild && XRE_IsContentProcess()) {
214
0
    timer->SetTarget(
215
0
      tabChild->TabGroup()->EventTargetFor(TaskCategory::Other));
216
0
  }
217
0
  RefPtr<DelayedFireSingleTapEvent> callback =
218
0
    new DelayedFireSingleTapEvent(mWidget, ldPoint, aModifiers, aClickCount,
219
0
        timer, touchRollup);
220
0
  nsresult rv = timer->InitWithCallback(callback,
221
0
                                        sActiveDurationMs,
222
0
                                        nsITimer::TYPE_ONE_SHOT);
223
0
  if (NS_FAILED(rv)) {
224
0
    // Make |callback| not hold the timer, so they will both be destructed when
225
0
    // we leave the scope of this function.
226
0
    callback->ClearTimer();
227
0
  }
228
0
}
229
230
bool
231
APZEventState::FireContextmenuEvents(const nsCOMPtr<nsIPresShell>& aPresShell,
232
                                     const CSSPoint& aPoint,
233
                                     const CSSToLayoutDeviceScale& aScale,
234
                                     Modifiers aModifiers,
235
                                     const nsCOMPtr<nsIWidget>& aWidget)
236
0
{
237
0
  // Converting the modifiers to DOM format for the DispatchMouseEvent call
238
0
  // is the most useless thing ever because nsDOMWindowUtils::SendMouseEvent
239
0
  // just converts them back to widget format, but that API has many callers,
240
0
  // including in JS code, so it's not trivial to change.
241
0
  bool eventHandled =
242
0
      APZCCallbackHelper::DispatchMouseEvent(aPresShell, NS_LITERAL_STRING("contextmenu"),
243
0
                         aPoint, 2, 1, WidgetModifiersToDOMModifiers(aModifiers), true,
244
0
                         dom::MouseEvent_Binding::MOZ_SOURCE_TOUCH,
245
0
                         0 /* Use the default value here. */);
246
0
247
0
  APZES_LOG("Contextmenu event handled: %d\n", eventHandled);
248
0
  if (eventHandled) {
249
0
    // If the contextmenu event was handled then we're showing a contextmenu,
250
0
    // and so we should remove any activation
251
0
    mActiveElementManager->ClearActivation();
252
0
#ifndef XP_WIN
253
0
  } else {
254
0
    // If the contextmenu wasn't consumed, fire the eMouseLongTap event.
255
0
    nsEventStatus status = APZCCallbackHelper::DispatchSynthesizedMouseEvent(
256
0
        eMouseLongTap, /*time*/ 0, aPoint * aScale, aModifiers,
257
0
        /*clickCount*/ 1, aWidget);
258
0
    eventHandled = (status == nsEventStatus_eConsumeNoDefault);
259
0
    APZES_LOG("eMouseLongTap event handled: %d\n", eventHandled);
260
0
#endif
261
0
  }
262
0
263
0
  return eventHandled;
264
0
}
265
266
void
267
APZEventState::ProcessLongTap(const nsCOMPtr<nsIPresShell>& aPresShell,
268
                              const CSSPoint& aPoint,
269
                              const CSSToLayoutDeviceScale& aScale,
270
                              Modifiers aModifiers,
271
                              const ScrollableLayerGuid& aGuid,
272
                              uint64_t aInputBlockId)
273
0
{
274
0
  APZES_LOG("Handling long tap at %s\n", Stringify(aPoint).c_str());
275
0
276
0
  nsCOMPtr<nsIWidget> widget = GetWidget();
277
0
  if (!widget) {
278
0
    return;
279
0
  }
280
0
281
0
  SendPendingTouchPreventedResponse(false);
282
0
283
#ifdef XP_WIN
284
  // On Windows, we fire the contextmenu events when the user lifts their
285
  // finger, in keeping with the platform convention. This happens in the
286
  // ProcessLongTapUp function. However, we still fire the eMouseLongTap event
287
  // at this time, because things like text selection or dragging may want
288
  // to know about it.
289
  nsEventStatus status = APZCCallbackHelper::DispatchSynthesizedMouseEvent(
290
      eMouseLongTap, /*time*/ 0, aPoint * aScale, aModifiers, /*clickCount*/ 1,
291
      widget);
292
293
  bool eventHandled = (status == nsEventStatus_eConsumeNoDefault);
294
#else
295
  bool eventHandled = FireContextmenuEvents(aPresShell, aPoint, aScale,
296
0
        aModifiers, widget);
297
0
#endif
298
0
  mContentReceivedInputBlockCallback(aGuid, aInputBlockId, eventHandled);
299
0
300
0
  if (eventHandled) {
301
0
    // Also send a touchcancel to content, so that listeners that might be
302
0
    // waiting for a touchend don't trigger.
303
0
    WidgetTouchEvent cancelTouchEvent(true, eTouchCancel, widget.get());
304
0
    cancelTouchEvent.mModifiers = aModifiers;
305
0
    auto ldPoint = LayoutDeviceIntPoint::Round(aPoint * aScale);
306
0
    cancelTouchEvent.mTouches.AppendElement(new mozilla::dom::Touch(mLastTouchIdentifier,
307
0
        ldPoint, LayoutDeviceIntPoint(), 0, 0));
308
0
    APZCCallbackHelper::DispatchWidgetEvent(cancelTouchEvent);
309
0
  }
310
0
}
311
312
void
313
APZEventState::ProcessLongTapUp(const nsCOMPtr<nsIPresShell>& aPresShell,
314
                                const CSSPoint& aPoint,
315
                                const CSSToLayoutDeviceScale& aScale,
316
                                Modifiers aModifiers)
317
0
{
318
#ifdef XP_WIN
319
  nsCOMPtr<nsIWidget> widget = GetWidget();
320
  if (widget) {
321
    FireContextmenuEvents(aPresShell, aPoint, aScale, aModifiers, widget);
322
  }
323
#endif
324
}
325
326
void
327
APZEventState::ProcessTouchEvent(const WidgetTouchEvent& aEvent,
328
                                 const ScrollableLayerGuid& aGuid,
329
                                 uint64_t aInputBlockId,
330
                                 nsEventStatus aApzResponse,
331
                                 nsEventStatus aContentResponse)
332
0
{
333
0
  if (aEvent.mMessage == eTouchStart && aEvent.mTouches.Length() > 0) {
334
0
    mActiveElementManager->SetTargetElement(aEvent.mTouches[0]->GetTarget());
335
0
    mLastTouchIdentifier = aEvent.mTouches[0]->Identifier();
336
0
  }
337
0
338
0
  bool isTouchPrevented = aContentResponse == nsEventStatus_eConsumeNoDefault;
339
0
  bool sentContentResponse = false;
340
0
  APZES_LOG("Handling event type %d\n", aEvent.mMessage);
341
0
  switch (aEvent.mMessage) {
342
0
  case eTouchStart: {
343
0
    mTouchEndCancelled = false;
344
0
    mTouchRollup = do_GetWeakReference(widget::nsAutoRollup::GetLastRollup());
345
0
346
0
    sentContentResponse = SendPendingTouchPreventedResponse(false);
347
0
    // sentContentResponse can be true here if we get two TOUCH_STARTs in a row
348
0
    // and just responded to the first one.
349
0
350
0
    // We're about to send a response back to APZ, but we should only do it
351
0
    // for events that went through APZ (which should be all of them).
352
0
    MOZ_ASSERT(aEvent.mFlags.mHandledByAPZ);
353
0
354
0
    if (isTouchPrevented) {
355
0
      mContentReceivedInputBlockCallback(aGuid, aInputBlockId, isTouchPrevented);
356
0
      sentContentResponse = true;
357
0
    } else {
358
0
      APZES_LOG("Event not prevented; pending response for %" PRIu64 " %s\n",
359
0
        aInputBlockId, Stringify(aGuid).c_str());
360
0
      mPendingTouchPreventedResponse = true;
361
0
      mPendingTouchPreventedGuid = aGuid;
362
0
      mPendingTouchPreventedBlockId = aInputBlockId;
363
0
    }
364
0
    break;
365
0
  }
366
0
367
0
  case eTouchEnd:
368
0
    if (isTouchPrevented) {
369
0
      mTouchEndCancelled = true;
370
0
      mEndTouchIsClick = false;
371
0
    }
372
0
    MOZ_FALLTHROUGH;
373
0
  case eTouchCancel:
374
0
    mActiveElementManager->HandleTouchEndEvent(mEndTouchIsClick);
375
0
    MOZ_FALLTHROUGH;
376
0
  case eTouchMove: {
377
0
    if (mPendingTouchPreventedResponse) {
378
0
      MOZ_ASSERT(aGuid == mPendingTouchPreventedGuid);
379
0
    }
380
0
    sentContentResponse = SendPendingTouchPreventedResponse(isTouchPrevented);
381
0
    break;
382
0
  }
383
0
384
0
  default:
385
0
    MOZ_ASSERT_UNREACHABLE("Unknown touch event type");
386
0
    break;
387
0
  }
388
0
389
0
  if (sentContentResponse && !isTouchPrevented &&
390
0
        aApzResponse == nsEventStatus_eConsumeDoDefault &&
391
0
        gfxPrefs::PointerEventsEnabled()) {
392
0
    WidgetTouchEvent cancelEvent(aEvent);
393
0
    cancelEvent.mMessage = eTouchPointerCancel;
394
0
    cancelEvent.mFlags.mCancelable = false; // mMessage != eTouchCancel;
395
0
    for (uint32_t i = 0; i < cancelEvent.mTouches.Length(); ++i) {
396
0
      if (mozilla::dom::Touch* touch = cancelEvent.mTouches[i]) {
397
0
        touch->convertToPointer = true;
398
0
      }
399
0
    }
400
0
    nsEventStatus status;
401
0
    cancelEvent.mWidget->DispatchEvent(&cancelEvent, status);
402
0
  }
403
0
}
404
405
void
406
APZEventState::ProcessWheelEvent(const WidgetWheelEvent& aEvent,
407
                                 const ScrollableLayerGuid& aGuid,
408
                                 uint64_t aInputBlockId)
409
0
{
410
0
  // If this event starts a swipe, indicate that it shouldn't result in a
411
0
  // scroll by setting defaultPrevented to true.
412
0
  bool defaultPrevented = aEvent.DefaultPrevented() || aEvent.TriggersSwipe();
413
0
  mContentReceivedInputBlockCallback(aGuid, aInputBlockId, defaultPrevented);
414
0
}
415
416
void
417
APZEventState::ProcessMouseEvent(const WidgetMouseEvent& aEvent,
418
                                 const ScrollableLayerGuid& aGuid,
419
                                 uint64_t aInputBlockId)
420
0
{
421
0
  bool defaultPrevented = false;
422
0
  mContentReceivedInputBlockCallback(aGuid, aInputBlockId, defaultPrevented);
423
0
}
424
425
void
426
APZEventState::ProcessAPZStateChange(ViewID aViewId,
427
                                     APZStateChange aChange,
428
                                     int aArg)
429
0
{
430
0
  switch (aChange)
431
0
  {
432
0
  case APZStateChange::eTransformBegin:
433
0
  {
434
0
    nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aViewId);
435
0
    if (sf) {
436
0
      sf->SetTransformingByAPZ(true);
437
0
    }
438
0
    nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(sf);
439
0
    if (scrollbarMediator) {
440
0
      scrollbarMediator->ScrollbarActivityStarted();
441
0
    }
442
0
443
0
    nsIContent* content = nsLayoutUtils::FindContentFor(aViewId);
444
0
    nsIDocument* doc = content ? content->GetComposedDoc() : nullptr;
445
0
    nsCOMPtr<nsIDocShell> docshell(doc ? doc->GetDocShell() : nullptr);
446
0
    if (docshell && sf) {
447
0
      nsDocShell* nsdocshell = static_cast<nsDocShell*>(docshell.get());
448
0
      nsdocshell->NotifyAsyncPanZoomStarted();
449
0
    }
450
0
    break;
451
0
  }
452
0
  case APZStateChange::eTransformEnd:
453
0
  {
454
0
    nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aViewId);
455
0
    if (sf) {
456
0
      sf->SetTransformingByAPZ(false);
457
0
    }
458
0
    nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(sf);
459
0
    if (scrollbarMediator) {
460
0
      scrollbarMediator->ScrollbarActivityStopped();
461
0
    }
462
0
463
0
    nsIContent* content = nsLayoutUtils::FindContentFor(aViewId);
464
0
    nsIDocument* doc = content ? content->GetComposedDoc() : nullptr;
465
0
    nsCOMPtr<nsIDocShell> docshell(doc ? doc->GetDocShell() : nullptr);
466
0
    if (docshell && sf) {
467
0
      nsDocShell* nsdocshell = static_cast<nsDocShell*>(docshell.get());
468
0
      nsdocshell->NotifyAsyncPanZoomStopped();
469
0
    }
470
0
    break;
471
0
  }
472
0
  case APZStateChange::eStartTouch:
473
0
  {
474
0
    mActiveElementManager->HandleTouchStart(aArg);
475
0
    break;
476
0
  }
477
0
  case APZStateChange::eStartPanning:
478
0
  {
479
0
    // The user started to pan, so we don't want anything to be :active.
480
0
    mActiveElementManager->ClearActivation();
481
0
    break;
482
0
  }
483
0
  case APZStateChange::eEndTouch:
484
0
  {
485
0
    mEndTouchIsClick = aArg;
486
0
    mActiveElementManager->HandleTouchEnd();
487
0
    break;
488
0
  }
489
0
  }
490
0
}
491
492
void
493
APZEventState::ProcessClusterHit()
494
0
{
495
0
  // If we hit a cluster of links then we shouldn't activate any of them,
496
0
  // as we will be showing the zoomed view. (This is only called on Fennec).
497
0
#ifndef MOZ_WIDGET_ANDROID
498
0
  MOZ_ASSERT(false);
499
0
#endif
500
0
  mActiveElementManager->ClearActivation();
501
0
}
502
503
bool
504
APZEventState::SendPendingTouchPreventedResponse(bool aPreventDefault)
505
0
{
506
0
  if (mPendingTouchPreventedResponse) {
507
0
    APZES_LOG("Sending response %d for pending guid: %s\n", aPreventDefault,
508
0
      Stringify(mPendingTouchPreventedGuid).c_str());
509
0
    mContentReceivedInputBlockCallback(mPendingTouchPreventedGuid,
510
0
        mPendingTouchPreventedBlockId, aPreventDefault);
511
0
    mPendingTouchPreventedResponse = false;
512
0
    return true;
513
0
  }
514
0
  return false;
515
0
}
516
517
already_AddRefed<nsIWidget>
518
APZEventState::GetWidget() const
519
0
{
520
0
  nsCOMPtr<nsIWidget> result = do_QueryReferent(mWidget);
521
0
  return result.forget();
522
0
}
523
524
already_AddRefed<nsIContent>
525
APZEventState::GetTouchRollup() const
526
0
{
527
0
  nsCOMPtr<nsIContent> result = do_QueryReferent(mTouchRollup);
528
0
  return result.forget();
529
0
}
530
531
} // namespace layers
532
} // namespace mozilla