Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/layout/base/TouchManager.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
8
#include "TouchManager.h"
9
10
#include "gfxPrefs.h"
11
#include "mozilla/dom/EventTarget.h"
12
#include "mozilla/PresShell.h"
13
#include "nsIFrame.h"
14
#include "nsView.h"
15
#include "PositionedEventTargeting.h"
16
17
using namespace mozilla::dom;
18
19
namespace mozilla {
20
21
nsDataHashtable<nsUint32HashKey, TouchManager::TouchInfo>* TouchManager::sCaptureTouchList;
22
23
/*static*/ void
24
TouchManager::InitializeStatics()
25
3
{
26
3
  NS_ASSERTION(!sCaptureTouchList, "InitializeStatics called multiple times!");
27
3
  sCaptureTouchList = new nsDataHashtable<nsUint32HashKey, TouchManager::TouchInfo>;
28
3
}
29
30
/*static*/ void
31
TouchManager::ReleaseStatics()
32
0
{
33
0
  NS_ASSERTION(sCaptureTouchList, "ReleaseStatics called without Initialize!");
34
0
  delete sCaptureTouchList;
35
0
  sCaptureTouchList = nullptr;
36
0
}
37
38
void
39
TouchManager::Init(PresShell* aPresShell, nsIDocument* aDocument)
40
0
{
41
0
  mPresShell = aPresShell;
42
0
  mDocument = aDocument;
43
0
}
44
45
void
46
TouchManager::Destroy()
47
0
{
48
0
  EvictTouches();
49
0
  mDocument = nullptr;
50
0
  mPresShell = nullptr;
51
0
}
52
53
static nsIContent*
54
GetNonAnonymousAncestor(EventTarget* aTarget)
55
0
{
56
0
  nsCOMPtr<nsIContent> content(do_QueryInterface(aTarget));
57
0
  if (content && content->IsInNativeAnonymousSubtree()) {
58
0
    content = content->FindFirstNonChromeOnlyAccessContent();
59
0
  }
60
0
  return content;
61
0
}
62
63
/*static*/ void
64
TouchManager::EvictTouchPoint(RefPtr<Touch>& aTouch,
65
                              nsIDocument* aLimitToDocument)
66
0
{
67
0
  nsCOMPtr<nsINode> node(do_QueryInterface(aTouch->mOriginalTarget));
68
0
  if (node) {
69
0
    nsIDocument* doc = node->GetComposedDoc();
70
0
    if (doc && (!aLimitToDocument || aLimitToDocument == doc)) {
71
0
      nsIPresShell* presShell = doc->GetShell();
72
0
      if (presShell) {
73
0
        nsIFrame* frame = presShell->GetRootFrame();
74
0
        if (frame) {
75
0
          nsPoint pt(aTouch->mRefPoint.x, aTouch->mRefPoint.y);
76
0
          nsCOMPtr<nsIWidget> widget = frame->GetView()->GetNearestWidget(&pt);
77
0
          if (widget) {
78
0
            WidgetTouchEvent event(true, eTouchEnd, widget);
79
0
            event.mTime = PR_IntervalNow();
80
0
            event.mTouches.AppendElement(aTouch);
81
0
            nsEventStatus status;
82
0
            widget->DispatchEvent(&event, status);
83
0
          }
84
0
        }
85
0
      }
86
0
    }
87
0
  }
88
0
  if (!node || !aLimitToDocument || node->OwnerDoc() == aLimitToDocument) {
89
0
    sCaptureTouchList->Remove(aTouch->Identifier());
90
0
  }
91
0
}
92
93
/*static*/ void
94
TouchManager::AppendToTouchList(WidgetTouchEvent::TouchArray* aTouchList)
95
0
{
96
0
  for (auto iter = sCaptureTouchList->Iter();
97
0
       !iter.Done();
98
0
       iter.Next()) {
99
0
    RefPtr<Touch>& touch = iter.Data().mTouch;
100
0
    touch->mChanged = false;
101
0
    aTouchList->AppendElement(touch);
102
0
  }
103
0
}
104
105
void
106
TouchManager::EvictTouches()
107
0
{
108
0
  WidgetTouchEvent::AutoTouchArray touches;
109
0
  AppendToTouchList(&touches);
110
0
  for (uint32_t i = 0; i < touches.Length(); ++i) {
111
0
    EvictTouchPoint(touches[i], mDocument);
112
0
  }
113
0
}
114
115
/* static */ nsIFrame*
116
TouchManager::SetupTarget(WidgetTouchEvent* aEvent, nsIFrame* aFrame)
117
0
{
118
0
  MOZ_ASSERT(aEvent);
119
0
120
0
  if (!aEvent || aEvent->mMessage != eTouchStart) {
121
0
    // All touch events except for touchstart use a captured target.
122
0
    return aFrame;
123
0
  }
124
0
125
0
  uint32_t flags = 0;
126
0
  // Setting this flag will skip the scrollbars on the root frame from
127
0
  // participating in hit-testing, and we only want that to happen on
128
0
  // zoomable platforms (for now).
129
0
  if (gfxPrefs::APZAllowZooming()) {
130
0
    flags |= INPUT_IGNORE_ROOT_SCROLL_FRAME;
131
0
  }
132
0
133
0
  nsIFrame* target = aFrame;
134
0
  for (int32_t i = aEvent->mTouches.Length(); i; ) {
135
0
    --i;
136
0
    dom::Touch* touch = aEvent->mTouches[i];
137
0
138
0
    int32_t id = touch->Identifier();
139
0
    if (!TouchManager::HasCapturedTouch(id)) {
140
0
      // find the target for this touch
141
0
      nsPoint eventPoint =
142
0
        nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, touch->mRefPoint,
143
0
                                                     aFrame);
144
0
      target = FindFrameTargetedByInputEvent(aEvent, aFrame, eventPoint, flags);
145
0
      if (target) {
146
0
        nsCOMPtr<nsIContent> targetContent;
147
0
        target->GetContentForEvent(aEvent, getter_AddRefs(targetContent));
148
0
        while (targetContent && !targetContent->IsElement()) {
149
0
          targetContent = targetContent->GetParent();
150
0
        }
151
0
        touch->SetTouchTarget(targetContent);
152
0
      } else {
153
0
        aEvent->mTouches.RemoveElementAt(i);
154
0
      }
155
0
    } else {
156
0
      // This touch is an old touch, we need to ensure that is not
157
0
      // marked as changed and set its target correctly
158
0
      touch->mChanged = false;
159
0
      RefPtr<dom::Touch> oldTouch = TouchManager::GetCapturedTouch(id);
160
0
      if (oldTouch) {
161
0
        touch->SetTouchTarget(oldTouch->mOriginalTarget);
162
0
      }
163
0
    }
164
0
  }
165
0
  return target;
166
0
}
167
168
/* static */ nsIFrame*
169
TouchManager::SuppressInvalidPointsAndGetTargetedFrame(WidgetTouchEvent* aEvent)
170
0
{
171
0
  MOZ_ASSERT(aEvent);
172
0
173
0
  if (!aEvent || aEvent->mMessage != eTouchStart) {
174
0
    // All touch events except for touchstart use a captured target.
175
0
    return nullptr;
176
0
  }
177
0
178
0
  // if this is a continuing session, ensure that all these events are
179
0
  // in the same document by taking the target of the events already in
180
0
  // the capture list
181
0
  nsCOMPtr<nsIContent> anyTarget;
182
0
  if (aEvent->mTouches.Length() > 1) {
183
0
    anyTarget = TouchManager::GetAnyCapturedTouchTarget();
184
0
  }
185
0
186
0
  nsIFrame* frame = nullptr;
187
0
  for (int32_t i = aEvent->mTouches.Length(); i; ) {
188
0
    --i;
189
0
    dom::Touch* touch = aEvent->mTouches[i];
190
0
    if (TouchManager::HasCapturedTouch(touch->Identifier())) {
191
0
      continue;
192
0
    }
193
0
194
0
    MOZ_ASSERT(touch->mOriginalTarget);
195
0
    nsCOMPtr<nsIContent> targetContent = do_QueryInterface(touch->GetTarget());
196
0
    nsIFrame* targetFrame = targetContent->GetPrimaryFrame();
197
0
    if (targetFrame && !anyTarget) {
198
0
      anyTarget = targetContent;
199
0
    } else {
200
0
      nsIFrame* newTargetFrame = nullptr;
201
0
      for (nsIFrame* f = targetFrame; f;
202
0
           f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
203
0
        if (f->PresContext()->Document() == anyTarget->OwnerDoc()) {
204
0
          newTargetFrame = f;
205
0
          break;
206
0
        }
207
0
        // We must be in a subdocument so jump directly to the root frame.
208
0
        // GetParentOrPlaceholderForCrossDoc gets called immediately to
209
0
        // jump up to the containing document.
210
0
        f = f->PresShell()->GetRootFrame();
211
0
      }
212
0
      // if we couldn't find a target frame in the same document as
213
0
      // anyTarget, remove the touch from the capture touch list, as
214
0
      // well as the event->mTouches array. touchmove events that aren't
215
0
      // in the captured touch list will be discarded
216
0
      if (!newTargetFrame) {
217
0
        touch->mIsTouchEventSuppressed = true;
218
0
      } else {
219
0
        targetFrame = newTargetFrame;
220
0
        targetFrame->GetContentForEvent(aEvent, getter_AddRefs(targetContent));
221
0
        while (targetContent && !targetContent->IsElement()) {
222
0
          targetContent = targetContent->GetParent();
223
0
        }
224
0
        touch->SetTouchTarget(targetContent);
225
0
      }
226
0
    }
227
0
    if (targetFrame) {
228
0
      frame = targetFrame;
229
0
    }
230
0
  }
231
0
  return frame;
232
0
}
233
234
bool
235
TouchManager::PreHandleEvent(WidgetEvent* aEvent,
236
                             nsEventStatus* aStatus,
237
                             bool& aTouchIsNew,
238
                             bool& aIsHandlingUserInput,
239
                             nsCOMPtr<nsIContent>& aCurrentEventContent)
240
0
{
241
0
  switch (aEvent->mMessage) {
242
0
    case eTouchStart: {
243
0
      aIsHandlingUserInput = true;
244
0
      WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
245
0
      // if there is only one touch in this touchstart event, assume that it is
246
0
      // the start of a new touch session and evict any old touches in the
247
0
      // queue
248
0
      if (touchEvent->mTouches.Length() == 1) {
249
0
        WidgetTouchEvent::AutoTouchArray touches;
250
0
        AppendToTouchList(&touches);
251
0
        for (uint32_t i = 0; i < touches.Length(); ++i) {
252
0
          EvictTouchPoint(touches[i]);
253
0
        }
254
0
      }
255
0
      // Add any new touches to the queue
256
0
      WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
257
0
      for (int32_t i = touches.Length(); i; ) {
258
0
        --i;
259
0
        Touch* touch = touches[i];
260
0
        int32_t id = touch->Identifier();
261
0
        if (!sCaptureTouchList->Get(id, nullptr)) {
262
0
          // If it is not already in the queue, it is a new touch
263
0
          touch->mChanged = true;
264
0
        }
265
0
        touch->mMessage = aEvent->mMessage;
266
0
        TouchInfo info = { touch, GetNonAnonymousAncestor(touch->mOriginalTarget),
267
0
                           true };
268
0
        sCaptureTouchList->Put(id, info);
269
0
        if (touch->mIsTouchEventSuppressed) {
270
0
          // We're going to dispatch touch event. Remove this touch instance if
271
0
          // it is suppressed.
272
0
          touches.RemoveElementAt(i);
273
0
          continue;
274
0
        }
275
0
      }
276
0
      break;
277
0
    }
278
0
    case eTouchMove: {
279
0
      // Check for touches that changed. Mark them add to queue
280
0
      WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
281
0
      WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
282
0
      bool haveChanged = false;
283
0
      for (int32_t i = touches.Length(); i; ) {
284
0
        --i;
285
0
        Touch* touch = touches[i];
286
0
        if (!touch) {
287
0
          continue;
288
0
        }
289
0
        int32_t id = touch->Identifier();
290
0
        touch->mMessage = aEvent->mMessage;
291
0
292
0
        TouchInfo info;
293
0
        if (!sCaptureTouchList->Get(id, &info)) {
294
0
          touches.RemoveElementAt(i);
295
0
          continue;
296
0
        }
297
0
        RefPtr<Touch> oldTouch = info.mTouch;
298
0
        if (!touch->Equals(oldTouch)) {
299
0
          touch->mChanged = true;
300
0
          haveChanged = true;
301
0
        }
302
0
303
0
        nsCOMPtr<EventTarget> targetPtr = oldTouch->mOriginalTarget;
304
0
        if (!targetPtr) {
305
0
          touches.RemoveElementAt(i);
306
0
          continue;
307
0
        }
308
0
        nsCOMPtr<nsINode> targetNode(do_QueryInterface(targetPtr));
309
0
        if (!targetNode->IsInComposedDoc()) {
310
0
          targetPtr = do_QueryInterface(info.mNonAnonymousTarget);
311
0
        }
312
0
        touch->SetTouchTarget(targetPtr);
313
0
314
0
        info.mTouch = touch;
315
0
        // info.mNonAnonymousTarget is still valid from above
316
0
        sCaptureTouchList->Put(id, info);
317
0
        // if we're moving from touchstart to touchmove for this touch
318
0
        // we allow preventDefault to prevent mouse events
319
0
        if (oldTouch->mMessage != touch->mMessage) {
320
0
          aTouchIsNew = true;
321
0
        }
322
0
        if (oldTouch->mIsTouchEventSuppressed) {
323
0
          touch->mIsTouchEventSuppressed = true;
324
0
          touches.RemoveElementAt(i);
325
0
          continue;
326
0
        }
327
0
      }
328
0
      // is nothing has changed, we should just return
329
0
      if (!haveChanged) {
330
0
        if (aTouchIsNew) {
331
0
          // however, if this is the first touchmove after a touchstart,
332
0
          // it is special in that preventDefault is allowed on it, so
333
0
          // we must dispatch it to content even if nothing changed. we
334
0
          // arbitrarily pick the first touch point to be the "changed"
335
0
          // touch because firing an event with no changed events doesn't
336
0
          // work.
337
0
          for (uint32_t i = 0; i < touchEvent->mTouches.Length(); ++i) {
338
0
            if (touchEvent->mTouches[i]) {
339
0
              touchEvent->mTouches[i]->mChanged = true;
340
0
              break;
341
0
            }
342
0
          }
343
0
        } else {
344
0
          return false;
345
0
        }
346
0
      }
347
0
      break;
348
0
    }
349
0
    case eTouchEnd:
350
0
      aIsHandlingUserInput = true;
351
0
      // Fall through to touchcancel code
352
0
      MOZ_FALLTHROUGH;
353
0
    case eTouchCancel: {
354
0
      // Remove the changed touches
355
0
      // need to make sure we only remove touches that are ending here
356
0
      WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
357
0
      WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
358
0
      for (int32_t i = touches.Length(); i; ) {
359
0
        --i;
360
0
        Touch* touch = touches[i];
361
0
        if (!touch) {
362
0
          continue;
363
0
        }
364
0
        touch->mMessage = aEvent->mMessage;
365
0
        touch->mChanged = true;
366
0
367
0
        int32_t id = touch->Identifier();
368
0
        TouchInfo info;
369
0
        if (!sCaptureTouchList->Get(id, &info)) {
370
0
          continue;
371
0
        }
372
0
        nsCOMPtr<EventTarget> targetPtr = info.mTouch->mOriginalTarget;
373
0
        nsCOMPtr<nsINode> targetNode(do_QueryInterface(targetPtr));
374
0
        if (targetNode && !targetNode->IsInComposedDoc()) {
375
0
          targetPtr = do_QueryInterface(info.mNonAnonymousTarget);
376
0
        }
377
0
378
0
        aCurrentEventContent = do_QueryInterface(targetPtr);
379
0
        touch->SetTouchTarget(targetPtr);
380
0
        sCaptureTouchList->Remove(id);
381
0
        if (info.mTouch->mIsTouchEventSuppressed) {
382
0
          touches.RemoveElementAt(i);
383
0
          continue;
384
0
        }
385
0
      }
386
0
      // add any touches left in the touch list, but ensure changed=false
387
0
      AppendToTouchList(&touches);
388
0
      break;
389
0
    }
390
0
    case eTouchPointerCancel: {
391
0
      // Don't generate pointer events by touch events after eTouchPointerCancel
392
0
      // is received.
393
0
      WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
394
0
      WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
395
0
      for (uint32_t i = 0; i < touches.Length(); ++i) {
396
0
        Touch* touch = touches[i];
397
0
        if (!touch) {
398
0
          continue;
399
0
        }
400
0
        int32_t id = touch->Identifier();
401
0
        TouchInfo info;
402
0
        if (!sCaptureTouchList->Get(id, &info)) {
403
0
          continue;
404
0
        }
405
0
        info.mConvertToPointer = false;
406
0
        sCaptureTouchList->Put(id, info);
407
0
      }
408
0
      break;
409
0
    }
410
0
    default:
411
0
      break;
412
0
  }
413
0
  return true;
414
0
}
415
416
/*static*/ already_AddRefed<nsIContent>
417
TouchManager::GetAnyCapturedTouchTarget()
418
0
{
419
0
  nsCOMPtr<nsIContent> result = nullptr;
420
0
  if (sCaptureTouchList->Count() == 0) {
421
0
    return result.forget();
422
0
  }
423
0
  for (auto iter = sCaptureTouchList->Iter(); !iter.Done(); iter.Next()) {
424
0
    RefPtr<Touch>& touch = iter.Data().mTouch;
425
0
    if (touch) {
426
0
      EventTarget* target = touch->GetTarget();
427
0
      if (target) {
428
0
        result = do_QueryInterface(target);
429
0
        break;
430
0
      }
431
0
    }
432
0
  }
433
0
  return result.forget();
434
0
}
435
436
/*static*/ bool
437
TouchManager::HasCapturedTouch(int32_t aId)
438
0
{
439
0
  return sCaptureTouchList->Contains(aId);
440
0
}
441
442
/*static*/ already_AddRefed<Touch>
443
TouchManager::GetCapturedTouch(int32_t aId)
444
0
{
445
0
  RefPtr<Touch> touch;
446
0
  TouchInfo info;
447
0
  if (sCaptureTouchList->Get(aId, &info)) {
448
0
    touch = info.mTouch;
449
0
  }
450
0
  return touch.forget();
451
0
}
452
453
/*static*/ bool
454
TouchManager::ShouldConvertTouchToPointer(const Touch* aTouch,
455
                                          const WidgetTouchEvent* aEvent)
456
0
{
457
0
  if (!aTouch || !aTouch->convertToPointer) {
458
0
    return false;
459
0
  }
460
0
  TouchInfo info;
461
0
  if (!sCaptureTouchList->Get(aTouch->Identifier(), &info)) {
462
0
    // This check runs before the TouchManager has the touch registered in its
463
0
    // touch list. It's because we dispatching pointer events before handling
464
0
    // touch events. So we convert eTouchStart to pointerdown even it's not
465
0
    // registered.
466
0
    // Check WidgetTouchEvent::mMessage because Touch::mMessage is assigned when
467
0
    // pre-handling touch events.
468
0
    return aEvent->mMessage == eTouchStart;
469
0
  }
470
0
  return info.mConvertToPointer;
471
0
}
472
473
} // namespace mozilla