File: TouchEventManager.cpp

package info (click to toggle)
chromium-browser 57.0.2987.98-1~deb8u1
  • links: PTS, VCS
  • area: main
  • in suites: jessie
  • size: 2,637,852 kB
  • ctags: 2,544,394
  • sloc: cpp: 12,815,961; ansic: 3,676,222; python: 1,147,112; asm: 526,608; java: 523,212; xml: 286,794; perl: 92,654; sh: 86,408; objc: 73,271; makefile: 27,698; cs: 18,487; yacc: 13,031; tcl: 12,957; pascal: 4,875; ml: 4,716; lex: 3,904; sql: 3,862; ruby: 1,982; lisp: 1,508; php: 1,368; exp: 404; awk: 325; csh: 117; jsp: 39; sed: 37
file content (514 lines) | stat: -rw-r--r-- 21,287 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "core/input/TouchEventManager.h"

#include "core/dom/Document.h"
#include "core/events/TouchEvent.h"
#include "core/frame/Deprecation.h"
#include "core/frame/EventHandlerRegistry.h"
#include "core/frame/FrameHost.h"
#include "core/frame/FrameView.h"
#include "core/html/HTMLCanvasElement.h"
#include "core/input/EventHandlingUtil.h"
#include "core/input/TouchActionUtil.h"
#include "core/layout/HitTestCanvasResult.h"
#include "core/page/ChromeClient.h"
#include "core/page/Page.h"
#include "platform/Histogram.h"
#include "platform/PlatformTouchEvent.h"
#include "wtf/CurrentTime.h"
#include "wtf/PtrUtil.h"
#include <memory>

namespace blink {

namespace {

bool hasTouchHandlers(const EventHandlerRegistry& registry) {
  return registry.hasEventHandlers(
             EventHandlerRegistry::TouchStartOrMoveEventBlocking) ||
         registry.hasEventHandlers(
             EventHandlerRegistry::TouchStartOrMoveEventPassive) ||
         registry.hasEventHandlers(
             EventHandlerRegistry::TouchEndOrCancelEventBlocking) ||
         registry.hasEventHandlers(
             EventHandlerRegistry::TouchEndOrCancelEventPassive);
}

const AtomicString& touchEventNameForTouchPointState(
    PlatformTouchPoint::TouchState state) {
  switch (state) {
    case PlatformTouchPoint::TouchReleased:
      return EventTypeNames::touchend;
    case PlatformTouchPoint::TouchCancelled:
      return EventTypeNames::touchcancel;
    case PlatformTouchPoint::TouchPressed:
      return EventTypeNames::touchstart;
    case PlatformTouchPoint::TouchMoved:
      return EventTypeNames::touchmove;
    case PlatformTouchPoint::TouchStationary:
    // Fall through to default
    default:
      ASSERT_NOT_REACHED();
      return emptyAtom;
  }
}

enum TouchEventDispatchResultType {
  UnhandledTouches,  // Unhandled touch events.
  HandledTouches,    // Handled touch events.
  TouchEventDispatchResultTypeMax,
};

// Defining this class type local to dispatchTouchEvents() and annotating
// it with STACK_ALLOCATED(), runs into MSVC(VS 2013)'s C4822 warning
// that the local class doesn't provide a local definition for 'operator new'.
// Which it intentionally doesn't and shouldn't.
//
// Work around such toolchain bugginess by lifting out the type, thereby
// taking it out of C4822's reach.
class ChangedTouches final {
  STACK_ALLOCATED();

 public:
  // The touches corresponding to the particular change state this struct
  // instance represents.
  Member<TouchList> m_touches;

  using EventTargetSet = HeapHashSet<Member<EventTarget>>;
  // Set of targets involved in m_touches.
  EventTargetSet m_targets;

  WebPointerProperties::PointerType m_pointerType;
};

}  // namespace

TouchEventManager::TouchEventManager(LocalFrame& frame) : m_frame(frame) {
  clear();
}

void TouchEventManager::clear() {
  m_touchSequenceDocument.clear();
  m_targetForTouchID.clear();
  m_regionForTouchID.clear();
  m_touchPressed = false;
  m_currentEvent = PlatformEvent::NoType;
  m_currentTouchAction = TouchActionAuto;
}

DEFINE_TRACE(TouchEventManager) {
  visitor->trace(m_frame);
  visitor->trace(m_touchSequenceDocument);
  visitor->trace(m_targetForTouchID);
}

WebInputEventResult TouchEventManager::dispatchTouchEvents(
    const PlatformTouchEvent& event,
    const HeapVector<TouchInfo>& touchInfos,
    bool allTouchesReleased) {
  // Build up the lists to use for the |touches|, |targetTouches| and
  // |changedTouches| attributes in the JS event. See
  // http://www.w3.org/TR/touch-events/#touchevent-interface for how these
  // lists fit together.

  // Holds the complete set of touches on the screen.
  TouchList* touches = TouchList::create();

  // A different view on the 'touches' list above, filtered and grouped by
  // event target. Used for the |targetTouches| list in the JS event.
  using TargetTouchesHeapMap = HeapHashMap<EventTarget*, Member<TouchList>>;
  TargetTouchesHeapMap touchesByTarget;

  // Array of touches per state, used to assemble the |changedTouches| list.
  ChangedTouches changedTouches[PlatformTouchPoint::TouchStateEnd];

  for (auto touchInfo : touchInfos) {
    const PlatformTouchPoint& point = touchInfo.point;
    PlatformTouchPoint::TouchState pointState = point.state();

    Touch* touch = Touch::create(
        touchInfo.targetFrame.get(), touchInfo.touchNode.get(), point.id(),
        point.screenPos(), touchInfo.contentPoint, touchInfo.adjustedRadius,
        point.rotationAngle(), point.force(), touchInfo.region);

    // Ensure this target's touch list exists, even if it ends up empty, so
    // it can always be passed to TouchEvent::Create below.
    TargetTouchesHeapMap::iterator targetTouchesIterator =
        touchesByTarget.find(touchInfo.touchNode.get());
    if (targetTouchesIterator == touchesByTarget.end()) {
      touchesByTarget.set(touchInfo.touchNode.get(), TouchList::create());
      targetTouchesIterator = touchesByTarget.find(touchInfo.touchNode.get());
    }

    // |touches| and |targetTouches| should only contain information about
    // touches still on the screen, so if this point is released or
    // cancelled it will only appear in the |changedTouches| list.
    if (pointState != PlatformTouchPoint::TouchReleased &&
        pointState != PlatformTouchPoint::TouchCancelled) {
      touches->append(touch);
      targetTouchesIterator->value->append(touch);
    }

    // Now build up the correct list for |changedTouches|.
    // Note that  any touches that are in the TouchStationary state (e.g. if
    // the user had several points touched but did not move them all) should
    // never be in the |changedTouches| list so we do not handle them
    // explicitly here. See https://bugs.webkit.org/show_bug.cgi?id=37609
    // for further discussion about the TouchStationary state.
    if (pointState != PlatformTouchPoint::TouchStationary &&
        touchInfo.knownTarget) {
      ASSERT(pointState < PlatformTouchPoint::TouchStateEnd);
      if (!changedTouches[pointState].m_touches)
        changedTouches[pointState].m_touches = TouchList::create();
      changedTouches[pointState].m_touches->append(touch);
      changedTouches[pointState].m_targets.add(touchInfo.touchNode);
      changedTouches[pointState].m_pointerType =
          point.pointerProperties().pointerType;
    }
  }

  if (allTouchesReleased) {
    m_touchSequenceDocument.clear();
    m_currentTouchAction = TouchActionAuto;
  }

  WebInputEventResult eventResult = WebInputEventResult::NotHandled;

  // Now iterate through the |changedTouches| list and |m_targets| within it,
  // sending TouchEvents to the targets as required.
  for (unsigned state = 0; state != PlatformTouchPoint::TouchStateEnd;
       ++state) {
    if (!changedTouches[state].m_touches)
      continue;

    const AtomicString& eventName(touchEventNameForTouchPointState(
        static_cast<PlatformTouchPoint::TouchState>(state)));
    for (const auto& eventTarget : changedTouches[state].m_targets) {
      EventTarget* touchEventTarget = eventTarget;
      TouchEvent* touchEvent = TouchEvent::create(
          touches, touchesByTarget.get(touchEventTarget),
          changedTouches[state].m_touches.get(), eventName,
          touchEventTarget->toNode()->document().domWindow(),
          event.getModifiers(), event.cancelable(),
          event.causesScrollingIfUncanceled(),
          event.touchStartOrFirstTouchMove(), event.timestamp(),
          m_currentTouchAction, changedTouches[state].m_pointerType);

      DispatchEventResult domDispatchResult =
          touchEventTarget->dispatchEvent(touchEvent);

      // Only report for top level documents with a single touch on
      // touch-start or the first touch-move.
      if (event.touchStartOrFirstTouchMove() && touchInfos.size() == 1 &&
          m_frame->isMainFrame()) {
        // Record the disposition and latency of touch starts and first touch
        // moves before and after the page is fully loaded respectively.
        int64_t latencyInMicros =
            (TimeTicks::Now() - event.timestamp()).InMicroseconds();
        if (event.cancelable()) {
          if (m_frame->document()->isLoadCompleted()) {
            DEFINE_STATIC_LOCAL(EnumerationHistogram,
                                touchDispositionsAfterPageLoadHistogram,
                                ("Event.Touch.TouchDispositionsAfterPageLoad",
                                 TouchEventDispatchResultTypeMax));
            touchDispositionsAfterPageLoadHistogram.count(
                (domDispatchResult != DispatchEventResult::NotCanceled)
                    ? HandledTouches
                    : UnhandledTouches);

            DEFINE_STATIC_LOCAL(
                CustomCountHistogram, eventLatencyAfterPageLoadHistogram,
                ("Event.Touch.TouchLatencyAfterPageLoad", 1, 100000000, 50));
            eventLatencyAfterPageLoadHistogram.count(latencyInMicros);
          } else {
            DEFINE_STATIC_LOCAL(EnumerationHistogram,
                                touchDispositionsBeforePageLoadHistogram,
                                ("Event.Touch.TouchDispositionsBeforePageLoad",
                                 TouchEventDispatchResultTypeMax));
            touchDispositionsBeforePageLoadHistogram.count(
                (domDispatchResult != DispatchEventResult::NotCanceled)
                    ? HandledTouches
                    : UnhandledTouches);

            DEFINE_STATIC_LOCAL(
                CustomCountHistogram, eventLatencyBeforePageLoadHistogram,
                ("Event.Touch.TouchLatencyBeforePageLoad", 1, 100000000, 50));
            eventLatencyBeforePageLoadHistogram.count(latencyInMicros);
          }
          // Report the touch disposition there is no active fling animation.
          DEFINE_STATIC_LOCAL(EnumerationHistogram,
                              touchDispositionsOutsideFlingHistogram,
                              ("Event.Touch.TouchDispositionsOutsideFling2",
                               TouchEventDispatchResultTypeMax));
          touchDispositionsOutsideFlingHistogram.count(
              (domDispatchResult != DispatchEventResult::NotCanceled)
                  ? HandledTouches
                  : UnhandledTouches);
        }

        // Report the touch disposition when there is an active fling animation.
        if (event.dispatchType() ==
            PlatformEvent::ListenersForcedNonBlockingDueToFling) {
          DEFINE_STATIC_LOCAL(EnumerationHistogram,
                              touchDispositionsDuringFlingHistogram,
                              ("Event.Touch.TouchDispositionsDuringFling2",
                               TouchEventDispatchResultTypeMax));
          touchDispositionsDuringFlingHistogram.count(
              touchEvent->preventDefaultCalledOnUncancelableEvent()
                  ? HandledTouches
                  : UnhandledTouches);
        }
      }
      eventResult = EventHandlingUtil::mergeEventResult(
          eventResult,
          EventHandlingUtil::toWebInputEventResult(domDispatchResult));
    }
  }

  return eventResult;
}

void TouchEventManager::updateTargetAndRegionMapsForTouchStarts(
    HeapVector<TouchInfo>& touchInfos) {
  for (auto& touchInfo : touchInfos) {
    // Touch events implicitly capture to the touched node, and don't change
    // active/hover states themselves (Gesture events do). So we only need
    // to hit-test on touchstart and when the target could be different than
    // the corresponding pointer event target.
    if (touchInfo.point.state() == PlatformTouchPoint::TouchPressed) {
      HitTestRequest::HitTestRequestType hitType = HitTestRequest::TouchEvent |
                                                   HitTestRequest::ReadOnly |
                                                   HitTestRequest::Active;
      HitTestResult result;
      // For the touchPressed points hit-testing is done in
      // PointerEventManager. If it was the second touch there is a
      // capturing documents for the touch and |m_touchSequenceDocument|
      // is not null. So if PointerEventManager should hit-test again
      // against |m_touchSequenceDocument| if the target set by
      // PointerEventManager was either null or not in
      // |m_touchSequenceDocument|.
      if (m_touchSequenceDocument &&
          (!touchInfo.touchNode ||
           &touchInfo.touchNode->document() != m_touchSequenceDocument)) {
        if (m_touchSequenceDocument->frame()) {
          LayoutPoint framePoint = LayoutPoint(
              m_touchSequenceDocument->frame()->view()->rootFrameToContents(
                  touchInfo.point.pos()));
          result = EventHandlingUtil::hitTestResultInFrame(
              m_touchSequenceDocument->frame(), framePoint, hitType);
          Node* node = result.innerNode();
          if (!node)
            continue;
          if (isHTMLCanvasElement(node)) {
            HitTestCanvasResult* hitTestCanvasResult =
                toHTMLCanvasElement(node)->getControlAndIdIfHitRegionExists(
                    result.pointInInnerNodeFrame());
            if (hitTestCanvasResult->getControl())
              node = hitTestCanvasResult->getControl();
            touchInfo.region = hitTestCanvasResult->getId();
          }
          // Touch events should not go to text nodes.
          if (node->isTextNode())
            node = FlatTreeTraversal::parent(*node);
          touchInfo.touchNode = node;
        } else {
          continue;
        }
      }
      if (!touchInfo.touchNode)
        continue;
      if (!m_touchSequenceDocument) {
        // Keep track of which document should receive all touch events
        // in the active sequence. This must be a single document to
        // ensure we don't leak Nodes between documents.
        m_touchSequenceDocument = &(touchInfo.touchNode->document());
        ASSERT(m_touchSequenceDocument->frame()->view());
      }

      // Ideally we'd ASSERT(!m_targetForTouchID.contains(point.id())
      // since we shouldn't get a touchstart for a touch that's already
      // down. However EventSender allows this to be violated and there's
      // some tests that take advantage of it. There may also be edge
      // cases in the browser where this happens.
      // See http://crbug.com/345372.
      m_targetForTouchID.set(touchInfo.point.id(), touchInfo.touchNode);

      m_regionForTouchID.set(touchInfo.point.id(), touchInfo.region);

      TouchAction effectiveTouchAction =
          TouchActionUtil::computeEffectiveTouchAction(*touchInfo.touchNode);
      if (effectiveTouchAction != TouchActionAuto) {
        m_frame->page()->chromeClient().setTouchAction(m_frame,
                                                       effectiveTouchAction);

        // Combine the current touch action sequence with the touch action
        // for the current finger press.
        m_currentTouchAction &= effectiveTouchAction;
      }
    }
  }
}

void TouchEventManager::setAllPropertiesOfTouchInfos(
    HeapVector<TouchInfo>& touchInfos) {
  for (auto& touchInfo : touchInfos) {
    PlatformTouchPoint::TouchState pointState = touchInfo.point.state();
    Node* touchNode = nullptr;
    String regionID;

    if (pointState == PlatformTouchPoint::TouchReleased ||
        pointState == PlatformTouchPoint::TouchCancelled) {
      // The target should be the original target for this touch, so get
      // it from the hashmap. As it's a release or cancel we also remove
      // it from the map.
      touchNode = m_targetForTouchID.take(touchInfo.point.id());
      regionID = m_regionForTouchID.take(touchInfo.point.id());
    } else {
      // No hittest is performed on move or stationary, since the target
      // is not allowed to change anyway.
      touchNode = m_targetForTouchID.get(touchInfo.point.id());
      regionID = m_regionForTouchID.get(touchInfo.point.id());
    }

    LocalFrame* targetFrame = nullptr;
    bool knownTarget = false;
    if (touchNode) {
      Document& doc = touchNode->document();
      // If the target node has moved to a new document while it was being
      // touched, we can't send events to the new document because that could
      // leak nodes from one document to another. See http://crbug.com/394339.
      if (&doc == m_touchSequenceDocument.get()) {
        targetFrame = doc.frame();
        knownTarget = true;
      }
    }
    if (!knownTarget) {
      // If we don't have a target registered for the point it means we've
      // missed our opportunity to do a hit test for it (due to some
      // optimization that prevented blink from ever seeing the
      // touchstart), or that the touch started outside the active touch
      // sequence document. We should still include the touch in the
      // Touches list reported to the application (eg. so it can
      // differentiate between a one and two finger gesture), but we won't
      // actually dispatch any events for it. Set the target to the
      // Document so that there's some valid node here. Perhaps this
      // should really be LocalDOMWindow, but in all other cases the target of
      // a Touch is a Node so using the window could be a breaking change.
      // Since we know there was no handler invoked, the specific target
      // should be completely irrelevant to the application.
      touchNode = m_touchSequenceDocument;
      targetFrame = m_touchSequenceDocument->frame();
    }
    ASSERT(targetFrame);

    // pagePoint should always be in the target element's document coordinates.
    FloatPoint pagePoint =
        targetFrame->view()->rootFrameToContents(touchInfo.point.pos());
    float scaleFactor = 1.0f / targetFrame->pageZoomFactor();

    touchInfo.touchNode = touchNode;
    touchInfo.targetFrame = targetFrame;
    touchInfo.contentPoint = pagePoint.scaledBy(scaleFactor);
    touchInfo.adjustedRadius = touchInfo.point.radius().scaledBy(scaleFactor);
    touchInfo.knownTarget = knownTarget;
    touchInfo.region = regionID;
  }
}

bool TouchEventManager::reHitTestTouchPointsIfNeeded(
    const PlatformTouchEvent& event,
    HeapVector<TouchInfo>& touchInfos) {
  bool newTouchSequence = true;
  bool allTouchesReleased = true;

  for (const auto& point : event.touchPoints()) {
    if (point.state() != PlatformTouchPoint::TouchPressed)
      newTouchSequence = false;
    if (point.state() != PlatformTouchPoint::TouchReleased &&
        point.state() != PlatformTouchPoint::TouchCancelled)
      allTouchesReleased = false;
  }
  if (newTouchSequence) {
    // Ideally we'd ASSERT(!m_touchSequenceDocument) here since we should
    // have cleared the active document when we saw the last release. But we
    // have some tests that violate this, ClusterFuzz could trigger it, and
    // there may be cases where the browser doesn't reliably release all
    // touches. http://crbug.com/345372 tracks this.
    m_touchSequenceDocument.clear();
  }

  ASSERT(m_frame->view());
  if (m_touchSequenceDocument && (!m_touchSequenceDocument->frame() ||
                                  !m_touchSequenceDocument->frame()->view())) {
    // If the active touch document has no frame or view, it's probably being
    // destroyed so we can't dispatch events.
    return false;
  }

  updateTargetAndRegionMapsForTouchStarts(touchInfos);

  m_touchPressed = !allTouchesReleased;

  // If there's no document receiving touch events, or no handlers on the
  // document set to receive the events, then we can skip all the rest of
  // this work.
  if (!m_touchSequenceDocument || !m_touchSequenceDocument->frameHost() ||
      !hasTouchHandlers(
          m_touchSequenceDocument->frameHost()->eventHandlerRegistry()) ||
      !m_touchSequenceDocument->frame()) {
    if (allTouchesReleased) {
      m_touchSequenceDocument.clear();
    }
    return false;
  }

  setAllPropertiesOfTouchInfos(touchInfos);

  return true;
}

// TODO(rbyers): Replace with AutoReset as base/WTF unification permits.
class CurrentEventHolder {
  // Always stack allocated to ensure lifetime doesn't exceed that of target
  DISALLOW_NEW();

 public:
  CurrentEventHolder(PlatformEvent::EventType& target,
                     PlatformEvent::EventType value)
      : m_target(target) {
    m_target = value;
  }
  ~CurrentEventHolder() { m_target = PlatformEvent::NoType; }

 private:
  PlatformEvent::EventType& m_target;
};

WebInputEventResult TouchEventManager::handleTouchEvent(
    const PlatformTouchEvent& event,
    HeapVector<TouchInfo>& touchInfos) {

  // Track the current event for the scope of this function.
  CurrentEventHolder holder(m_currentEvent, event.type());

  if (!reHitTestTouchPointsIfNeeded(event, touchInfos))
    return WebInputEventResult::NotHandled;

  bool allTouchesReleased = true;
  for (const auto& point : event.touchPoints()) {
    if (point.state() != PlatformTouchPoint::TouchReleased &&
        point.state() != PlatformTouchPoint::TouchCancelled)
      allTouchesReleased = false;
  }

  return dispatchTouchEvents(event, touchInfos, allTouchesReleased);
}

bool TouchEventManager::isAnyTouchActive() const {
  return m_touchPressed;
}

}  // namespace blink