Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(83)

Side by Side Diff: third_party/WebKit/Source/core/input/TouchEventManager.cpp

Issue 1892653003: Extract touch handling logic from EventHandler (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "core/input/TouchEventManager.h"
6
7 #include "core/dom/Document.h"
8 #include "core/events/TouchEvent.h"
9 #include "core/frame/EventHandlerRegistry.h"
10 #include "core/frame/FrameHost.h"
11 #include "core/frame/FrameView.h"
12 #include "core/html/HTMLCanvasElement.h"
13 #include "core/input/EventHandler.h"
14 #include "core/input/TouchActionUtil.h"
15 #include "core/page/ChromeClient.h"
16 #include "core/page/Page.h"
17 #include "platform/Histogram.h"
18 #include "platform/PlatformTouchEvent.h"
19
20
21
22 namespace blink {
23
24 namespace {
25
26 bool hasTouchHandlers(const EventHandlerRegistry& registry)
27 {
28 return registry.hasEventHandlers(EventHandlerRegistry::TouchStartOrMoveEvent Blocking)
29 || registry.hasEventHandlers(EventHandlerRegistry::TouchStartOrMoveEvent Passive)
30 || registry.hasEventHandlers(EventHandlerRegistry::TouchEndOrCancelEvent Blocking)
31 || registry.hasEventHandlers(EventHandlerRegistry::TouchEndOrCancelEvent Passive);
32 }
33
34 const AtomicString& touchEventNameForTouchPointState(PlatformTouchPoint::TouchSt ate state)
35 {
36 switch (state) {
37 case PlatformTouchPoint::TouchReleased:
38 return EventTypeNames::touchend;
39 case PlatformTouchPoint::TouchCancelled:
40 return EventTypeNames::touchcancel;
41 case PlatformTouchPoint::TouchPressed:
42 return EventTypeNames::touchstart;
43 case PlatformTouchPoint::TouchMoved:
44 return EventTypeNames::touchmove;
45 case PlatformTouchPoint::TouchStationary:
46 // Fall through to default
47 default:
48 ASSERT_NOT_REACHED();
49 return emptyAtom;
50 }
51 }
52
53 // These offsets change indicies into the ListenerHistogram
54 // enumeration. The addition of a series of offsets then
55 // produces the resulting ListenerHistogram value.
56 const size_t kTouchTargetHistogramRootScrollerOffset = 4;
57 const size_t kTouchTargetHistogramScrollableDocumentOffset = 2;
58 const size_t kTouchTargetHistogramHandledOffset = 1;
59
60 enum TouchTargetAndDispatchResultType {
61 NonRootScrollerNonScrollableNotHandled, // Non-root-scroller, non-scrollable document, not handled.
62 NonRootScrollerNonScrollableHandled, // Non-root-scroller, non-scrollable do cument, handled application.
63 NonRootScrollerScrollableDocumentNotHandled, // Non-root-scroller, scrollabl e document, not handled.
64 NonRootScrollerScrollableDocumentHandled, // Non-root-scroller, scrollable d ocument, handled application.
65 RootScrollerNonScrollableNotHandled, // Root-scroller, non-scrollable docume nt, not handled.
66 RootScrollerNonScrollableHandled, // Root-scroller, non-scrollable document, handled.
67 RootScrollerScrollableDocumentNotHandled, // Root-scroller, scrollable docum ent, not handled.
68 RootScrollerScrollableDocumentHandled, // Root-scroller, scrollable document , handled.
69 TouchTargetAndDispatchResultTypeMax,
70 };
71
72 TouchTargetAndDispatchResultType toTouchTargetHistogramValue(EventTarget* eventT arget, DispatchEventResult dispatchResult)
73 {
74 int result = 0;
75 Document* document = nullptr;
76
77 if (const LocalDOMWindow* domWindow = eventTarget->toLocalDOMWindow()) {
78 // Treat the window as a root scroller as well.
79 document = domWindow->document();
80 result += kTouchTargetHistogramRootScrollerOffset;
81 } else if (Node* node = eventTarget->toNode()) {
82 // Report if the target node is the document or body.
83 if (node->isDocumentNode() || static_cast<Node*>(node->document().docume ntElement()) == node || static_cast<Node*>(node->document().body()) == node) {
84 result += kTouchTargetHistogramRootScrollerOffset;
85 }
86 document = &node->document();
87 }
88
89 if (document) {
90 FrameView* view = document->view();
91 if (view && view->isScrollable())
92 result += kTouchTargetHistogramScrollableDocumentOffset;
93 }
94
95 if (dispatchResult != DispatchEventResult::NotCanceled)
96 result += kTouchTargetHistogramHandledOffset;
97 return static_cast<TouchTargetAndDispatchResultType>(result);
98 }
99
100 }
dtapuska 2016/04/27 17:07:04 ending comment terminating the namespace " // name
Navid Zolghadr 2016/04/28 15:13:11 Done.
101
102 TouchEventManager::TouchEventManager(LocalFrame* frame)
103 : m_frame(frame)
104 {
105 clear();
106 }
107
108 TouchEventManager::~TouchEventManager()
109 {
110 }
111
112 namespace {
dtapuska 2016/04/27 17:07:05 Why double annonymous namespaces; can we collapse
Navid Zolghadr 2016/04/28 15:13:11 It was the case from EventHandler. So I thought th
113
114 // Defining this class type local to dispatchTouchEvents() and annotating
115 // it with STACK_ALLOCATED(), runs into MSVC(VS 2013)'s C4822 warning
116 // that the local class doesn't provide a local definition for 'operator new'.
117 // Which it intentionally doesn't and shouldn't.
118 //
119 // Work around such toolchain bugginess by lifting out the type, thereby
120 // taking it out of C4822's reach.
121 class ChangedTouches final {
122 STACK_ALLOCATED();
123 public:
124 // The touches corresponding to the particular change state this struct
125 // instance represents.
126 Member<TouchList> m_touches;
127
128 using EventTargetSet = HeapHashSet<Member<EventTarget>>;
129 // Set of targets involved in m_touches.
130 EventTargetSet m_targets;
131 };
132
133 } // namespace
134
135 WebInputEventResult TouchEventManager::dispatchTouchEvents(
136 const PlatformTouchEvent& event,
137 const HeapVector<TouchInfo>& touchInfos)
138 {
139 bool touchStartOrFirstTouchMove = false;
140 if (event.type() == PlatformEvent::TouchStart) {
141 m_waitingForFirstTouchMove = true;
142 touchStartOrFirstTouchMove = true;
143 } else if (event.type() == PlatformEvent::TouchMove) {
144 touchStartOrFirstTouchMove = m_waitingForFirstTouchMove;
145 m_waitingForFirstTouchMove = false;
146 }
147
148 // Build up the lists to use for the 'touches', 'targetTouches' and
149 // 'changedTouches' attributes in the JS event. See
150 // http://www.w3.org/TR/touch-events/#touchevent-interface for how these
151 // lists fit together.
152
153 // Holds the complete set of touches on the screen.
154 TouchList* touches = TouchList::create();
155
156 // A different view on the 'touches' list above, filtered and grouped by
157 // event target. Used for the 'targetTouches' list in the JS event.
158 using TargetTouchesHeapMap = HeapHashMap<EventTarget*, Member<TouchList>>;
159 TargetTouchesHeapMap touchesByTarget;
160
161 // Array of touches per state, used to assemble the 'changedTouches' list.
162 ChangedTouches changedTouches[PlatformTouchPoint::TouchStateEnd];
163
164 for (unsigned i = 0; i < touchInfos.size(); ++i) {
165 const TouchInfo& touchInfo = touchInfos[i];
166 const PlatformTouchPoint& point = touchInfo.point;
167 PlatformTouchPoint::TouchState pointState = point.state();
168
169 if (touchInfo.consumed)
170 continue;
171
172 Touch* touch = Touch::create(
173 touchInfo.targetFrame.get(),
174 touchInfo.touchNode.get(),
175 point.id(),
176 point.screenPos(),
177 touchInfo.adjustedPagePoint,
178 touchInfo.adjustedRadius,
179 point.rotationAngle(),
180 point.force(),
181 touchInfo.region);
182
183 // Ensure this target's touch list exists, even if it ends up empty, so
184 // it can always be passed to TouchEvent::Create below.
185 TargetTouchesHeapMap::iterator targetTouchesIterator = touchesByTarget.f ind(touchInfo.touchNode.get());
186 if (targetTouchesIterator == touchesByTarget.end()) {
187 touchesByTarget.set(touchInfo.touchNode.get(), TouchList::create());
188 targetTouchesIterator = touchesByTarget.find(touchInfo.touchNode.get ());
189 }
190
191 // touches and targetTouches should only contain information about
dtapuska 2016/04/27 17:07:04 Refer to variables enclosed in |
Navid Zolghadr 2016/04/28 15:13:11 There was quite a few occurrence of this in this e
192 // touches still on the screen, so if this point is released or
193 // cancelled it will only appear in the changedTouches list.
194 if (pointState != PlatformTouchPoint::TouchReleased && pointState != Pla tformTouchPoint::TouchCancelled) {
195 touches->append(touch);
196 targetTouchesIterator->value->append(touch);
197 }
198
199 // Now build up the correct list for changedTouches.
200 // Note that any touches that are in the TouchStationary state (e.g. if
dtapuska 2016/04/27 17:07:05 ditto
Navid Zolghadr 2016/04/28 15:13:11 Done.
201 // the user had several points touched but did not move them all) should
202 // never be in the changedTouches list so we do not handle them
203 // explicitly here. See https://bugs.webkit.org/show_bug.cgi?id=37609
204 // for further discussion about the TouchStationary state.
205 if (pointState != PlatformTouchPoint::TouchStationary && touchInfo.known Target) {
206 ASSERT(pointState < PlatformTouchPoint::TouchStateEnd);
207 if (!changedTouches[pointState].m_touches)
208 changedTouches[pointState].m_touches = TouchList::create();
209 changedTouches[pointState].m_touches->append(touch);
210 changedTouches[pointState].m_targets.add(touchInfo.touchNode);
211 }
212 }
213
214 WebInputEventResult eventResult = WebInputEventResult::NotHandled;
215
216 // Now iterate through the changedTouches list and m_targets within it, send ing
217 // TouchEvents to the targets as required.
218 for (unsigned state = 0; state != PlatformTouchPoint::TouchStateEnd; ++state ) {
219 if (!changedTouches[state].m_touches)
220 continue;
221
222 const AtomicString& eventName(touchEventNameForTouchPointState(static_ca st<PlatformTouchPoint::TouchState>(state)));
223 for (const auto& eventTarget : changedTouches[state].m_targets) {
224 EventTarget* touchEventTarget = eventTarget;
225 TouchEvent* touchEvent = TouchEvent::create(
226 touches, touchesByTarget.get(touchEventTarget), changedTouches[s tate].m_touches.get(),
227 eventName, touchEventTarget->toNode()->document().domWindow(),
228 event.getModifiers(), event.cancelable(), event.causesScrollingI fUncanceled(), event.timestamp());
229
230 DispatchEventResult domDispatchResult = touchEventTarget->dispatchEv ent(touchEvent);
231
232 // Only report for top level documents with a single touch on
233 // touch-start or the first touch-move.
234 if (touchStartOrFirstTouchMove && touchInfos.size() == 1 && event.ca ncelable() && !m_frame->document()->ownerElement()) {
235 DEFINE_STATIC_LOCAL(EnumerationHistogram, rootDocumentListenerHi stogram, ("Event.Touch.TargetAndDispatchResult", TouchTargetAndDispatchResultTyp eMax));
236 rootDocumentListenerHistogram.count(toTouchTargetHistogramValue( eventTarget, domDispatchResult));
237 }
238 eventResult = EventHandler::mergeEventResult(eventResult,
239 EventHandler::toWebInputEventResult(domDispatchResult));
240 }
241 }
242
243 return eventResult;
244 }
245
246 void TouchEventManager::updateTargetAndRegionMapsForTouchStarts(
247 HeapVector<TouchInfo>& touchInfos)
248 {
249 for (auto& touchInfo : touchInfos) {
250 // Touch events implicitly capture to the touched node, and don't change
251 // active/hover states themselves (Gesture events do). So we only need
252 // to hit-test on touchstart and when the target could be different than
253 // the corresponding pointer event target.
254 if (touchInfo.point.state() == PlatformTouchPoint::TouchPressed) {
255 HitTestRequest::HitTestRequestType hitType = HitTestRequest::TouchEv ent | HitTestRequest::ReadOnly | HitTestRequest::Active;
256 LayoutPoint pagePoint = roundedLayoutPoint(m_frame->view()->rootFram eToContents(touchInfo.point.pos()));
257 HitTestResult result;
258 if (!m_touchSequenceDocument) {
259 result = m_frame->eventHandler().hitTestResultAtPoint(pagePoint, hitType);
260 } else if (m_touchSequenceDocument->frame()) {
261 LayoutPoint framePoint = roundedLayoutPoint(m_touchSequenceDocum ent->frame()->view()->rootFrameToContents(touchInfo.point.pos()));
262 result = EventHandler::hitTestResultInFrame(m_touchSequenceDocum ent->frame(), framePoint, hitType);
263 } else {
264 continue;
265 }
266
267 Node* node = result.innerNode();
268 if (!node)
269 continue;
270 if (isHTMLCanvasElement(node)) {
271 std::pair<Element*, String> regionInfo = toHTMLCanvasElement(nod e)->getControlAndIdIfHitRegionExists(result.pointInInnerNodeFrame());
272 if (regionInfo.first)
273 node = regionInfo.first;
274 touchInfo.region = regionInfo.second;
275 }
276 // Touch events should not go to text nodes.
277 if (node->isTextNode())
278 node = FlatTreeTraversal::parent(*node);
279 touchInfo.touchNode = node;
280
mustaq 2016/04/27 14:36:11 Double empty-lines.
Navid Zolghadr 2016/04/28 15:13:11 Done.
281
282 if (!m_touchSequenceDocument) {
283 // Keep track of which document should receive all touch events
284 // in the active sequence. This must be a single document to
285 // ensure we don't leak Nodes between documents.
286 m_touchSequenceDocument = &(touchInfo.touchNode->document());
287 ASSERT(m_touchSequenceDocument->frame()->view());
288 }
289
290 // Ideally we'd ASSERT(!m_targetForTouchID.contains(point.id())
291 // since we shouldn't get a touchstart for a touch that's already
292 // down. However EventSender allows this to be violated and there's
293 // some tests that take advantage of it. There may also be edge
294 // cases in the browser where this happens.
295 // See http://crbug.com/345372.
296 m_targetForTouchID.set(touchInfo.point.id(), touchInfo.touchNode);
297
298 m_regionForTouchID.set(touchInfo.point.id(), touchInfo.region);
299
300 TouchAction effectiveTouchAction =
301 TouchActionUtil::computeEffectiveTouchAction(
302 *touchInfo.touchNode);
303 if (effectiveTouchAction != TouchActionAuto)
304 m_frame->page()->chromeClient().setTouchAction(effectiveTouchAct ion);
305 }
306 }
307 }
308
309 void TouchEventManager::setAllPropertiesOfTouchInfos(
310 HeapVector<TouchInfo>& touchInfos)
311 {
312 for (auto& touchInfo : touchInfos) {
313 PlatformTouchPoint::TouchState pointState = touchInfo.point.state();
314 Node* touchNode = nullptr;
315 String regionID;
316
317 if (pointState == PlatformTouchPoint::TouchReleased
318 || pointState == PlatformTouchPoint::TouchCancelled) {
319 // The target should be the original target for this touch, so get
320 // it from the hashmap. As it's a release or cancel we also remove
321 // it from the map.
322 touchNode = m_targetForTouchID.take(touchInfo.point.id());
323 regionID = m_regionForTouchID.take(touchInfo.point.id());
324 } else {
325 // No hittest is performed on move or stationary, since the target
326 // is not allowed to change anyway.
327 touchNode = m_targetForTouchID.get(touchInfo.point.id());
328 regionID = m_regionForTouchID.get(touchInfo.point.id());
329 }
330
331 LocalFrame* targetFrame = nullptr;
332 bool knownTarget = false;
333 if (touchNode) {
334 Document& doc = touchNode->document();
335 // If the target node has moved to a new document while it was being touched,
336 // we can't send events to the new document because that could leak nodes
337 // from one document to another. See http://crbug.com/394339.
338 if (&doc == m_touchSequenceDocument.get()) {
339 targetFrame = doc.frame();
340 knownTarget = true;
341 }
342 }
343 if (!knownTarget) {
344 // If we don't have a target registered for the point it means we've
345 // missed our opportunity to do a hit test for it (due to some
346 // optimization that prevented blink from ever seeing the
347 // touchstart), or that the touch started outside the active touch
348 // sequence document. We should still include the touch in the
349 // Touches list reported to the application (eg. so it can
350 // differentiate between a one and two finger gesture), but we won't
351 // actually dispatch any events for it. Set the target to the
352 // Document so that there's some valid node here. Perhaps this
353 // should really be LocalDOMWindow, but in all other cases the targe t of
354 // a Touch is a Node so using the window could be a breaking change.
355 // Since we know there was no handler invoked, the specific target
356 // should be completely irrelevant to the application.
357 touchNode = m_touchSequenceDocument;
358 targetFrame = m_touchSequenceDocument->frame();
359 }
360 ASSERT(targetFrame);
361
362 // pagePoint should always be in the target element's document coordinat es.
363 FloatPoint pagePoint = targetFrame->view()->rootFrameToContents(
364 touchInfo.point.pos());
365 float scaleFactor = 1.0f / targetFrame->pageZoomFactor();
366
367 touchInfo.touchNode = touchNode;
368 touchInfo.targetFrame = targetFrame;
369 touchInfo.adjustedPagePoint = pagePoint.scaledBy(scaleFactor);
370 touchInfo.adjustedRadius = touchInfo.point.radius().scaledBy(scaleFactor );
371 touchInfo.knownTarget = knownTarget;
372 touchInfo.consumed = false;
373 touchInfo.region = regionID;
374 }
375 }
376
377 bool TouchEventManager::generateTouchInfos(
mustaq 2016/04/27 14:36:11 Make the hittesting explicit in the name here so t
Navid Zolghadr 2016/04/28 15:13:11 Done.
378 const PlatformTouchEvent& event,
379 HeapVector<TouchInfo>& touchInfos)
380 {
381 bool newTouchSequence = true;
382 bool allTouchReleased = true;
383
384 for (const auto& point : event.touchPoints()) {
385 if (point.state() != PlatformTouchPoint::TouchPressed)
386 newTouchSequence = false;
387 if (point.state() != PlatformTouchPoint::TouchReleased && point.state() != PlatformTouchPoint::TouchCancelled)
388 allTouchReleased = false;
389 }
390 if (newTouchSequence) {
391 // Ideally we'd ASSERT !m_touchSequenceDocument here since we should
392 // have cleared the active document when we saw the last release. But we
393 // have some tests that violate this, ClusterFuzz could trigger it, and
394 // there may be cases where the browser doesn't reliably release all
395 // touches. http://crbug.com/345372 tracks this.
396 m_touchSequenceDocument.clear();
397 m_touchSequenceUserGestureToken.clear();
398 }
399
400 ASSERT(m_frame->view());
401 if (m_touchSequenceDocument && (!m_touchSequenceDocument->frame() || !m_touc hSequenceDocument->frame()->view())) {
402 // If the active touch document has no frame or view, it's probably bein g destroyed
403 // so we can't dispatch events.
404 return false;
405 }
406
407 for (const auto& point : event.touchPoints()) {
408 TouchEventManager::TouchInfo touchInfo;
409 touchInfo.point = point;
410 touchInfos.append(touchInfo);
411 }
412
413 updateTargetAndRegionMapsForTouchStarts(touchInfos);
414
415 m_touchPressed = !allTouchReleased;
416
417 // If there's no document receiving touch events, or no handlers on the
418 // document set to receive the events, then we can skip all the rest of
419 // this work.
420 if (!m_touchSequenceDocument || !m_touchSequenceDocument->frameHost() || !ha sTouchHandlers(m_touchSequenceDocument->frameHost()->eventHandlerRegistry()) || !m_touchSequenceDocument->frame()) {
421 if (allTouchReleased) {
422 m_touchSequenceDocument.clear();
423 m_touchSequenceUserGestureToken.clear();
424 }
425 return false;
426 }
427
428 // Whether a touch should be considered a "user gesture" or not is a tricky question.
429 // https://docs.google.com/document/d/1oF1T3O7_E4t1PYHV6gyCwHxOi3ystm0eSL5xZ u7nvOg/edit#
430 // TODO(rbyers): Disable user gesture in some cases but retain logging for n ow (crbug.com/582140).
431 OwnPtr<UserGestureIndicator> gestureIndicator;
432 if (event.touchPoints().size() == 1
433 && event.touchPoints()[0].state() == PlatformTouchPoint::TouchReleased
434 && !event.causesScrollingIfUncanceled()) {
435 // This is a touchend corresponding to a tap, definitely a user gesture. So don't supply
436 // a UserGestureUtilizedCallback.
437 gestureIndicator = adoptPtr(new UserGestureIndicator(DefinitelyProcessin gUserGesture));
438 } else {
439 // This is some other touch event that perhaps shouldn't be considered a user gesture. So
440 // use a UserGestureUtilizedCallback to get metrics / deprecation warnin gs.
441 if (m_touchSequenceUserGestureToken)
442 gestureIndicator = adoptPtr(new UserGestureIndicator(m_touchSequence UserGestureToken.release(), &m_touchSequenceDocument->frame()->eventHandler()));
443 else
444 gestureIndicator = adoptPtr(new UserGestureIndicator(DefinitelyProce ssingUserGesture, &m_touchSequenceDocument->frame()->eventHandler()));
445 m_touchSequenceUserGestureToken = UserGestureIndicator::currentToken();
446 }
447
448 setAllPropertiesOfTouchInfos(touchInfos);
449
450 if (allTouchReleased) {
451 m_touchSequenceDocument.clear();
452 m_touchSequenceUserGestureToken.clear();
453 }
454
455 return true;
456 }
457
458 WebInputEventResult TouchEventManager::handleTouchEvent(
459 const PlatformTouchEvent& event,
460 const HeapVector<TouchInfo>& touchInfos)
461 {
462 // Note that the disposition of any pointer events affects only the generati on of touch
463 // events. If all pointer events were handled (and hence no touch events wer e fired), that
464 // is still equivalent to the touch events going unhandled because pointer e vent handler
465 // don't block scroll gesture generation.
466
467 // TODO(crbug.com/507408): If PE handlers always call preventDefault, we won 't see TEs until after
468 // scrolling starts because the scrolling would suppress upcoming PEs. This sudden "break" in TE
469 // suppression can make the visible TEs inconsistent (e.g. touchmove without a touchstart).
470
471 return dispatchTouchEvents(event, touchInfos);
472 }
473
474 void TouchEventManager::clear()
475 {
476 m_touchSequenceDocument.clear();
477 m_touchSequenceUserGestureToken.clear();
478 m_targetForTouchID.clear();
479 m_regionForTouchID.clear();
480 m_touchPressed = false;
481 m_waitingForFirstTouchMove = false;
482 }
483
484 bool TouchEventManager::isAnyTouchActive() const
485 {
486 return m_touchPressed;
487 }
488
489 DEFINE_TRACE(TouchEventManager)
490 {
491 visitor->trace(m_frame);
492 visitor->trace(m_touchSequenceDocument);
493 visitor->trace(m_targetForTouchID);
494 }
495
496 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698