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

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: Rebased 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 }
101
102 TouchEventManager::TouchEventManager(LocalFrame* frame)
103 : m_frame(frame)
104 {
105 clear();
106 }
107
108 TouchEventManager::~TouchEventManager()
109 {
110 }
111
112 namespace {
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 HeapVector<TouchInfo>& touchInfos,
138 bool allTouchReleased)
139 {
140 bool touchStartOrFirstTouchMove = false;
141 if (event.type() == PlatformEvent::TouchStart) {
142 m_waitingForFirstTouchMove = true;
143 touchStartOrFirstTouchMove = true;
144 } else if (event.type() == PlatformEvent::TouchMove) {
145 touchStartOrFirstTouchMove = m_waitingForFirstTouchMove;
146 m_waitingForFirstTouchMove = false;
147 }
148
149 // Build up the lists to use for the 'touches', 'targetTouches' and
150 // 'changedTouches' attributes in the JS event. See
151 // http://www.w3.org/TR/touch-events/#touchevent-interface for how these
152 // lists fit together.
153
154 // Holds the complete set of touches on the screen.
155 TouchList* touches = TouchList::create();
156
157 // A different view on the 'touches' list above, filtered and grouped by
158 // event target. Used for the 'targetTouches' list in the JS event.
159 using TargetTouchesHeapMap = HeapHashMap<EventTarget*, Member<TouchList>>;
160 TargetTouchesHeapMap touchesByTarget;
161
162 // Array of touches per state, used to assemble the 'changedTouches' list.
163 ChangedTouches changedTouches[PlatformTouchPoint::TouchStateEnd];
164
165 for (unsigned i = 0; i < touchInfos.size(); ++i) {
166 const TouchInfo& touchInfo = touchInfos[i];
167 const PlatformTouchPoint& point = touchInfo.point;
168 PlatformTouchPoint::TouchState pointState = point.state();
169
170 Touch* touch = Touch::create(
171 touchInfo.targetFrame.get(),
172 touchInfo.touchNode.get(),
173 point.id(),
174 point.screenPos(),
175 touchInfo.adjustedPagePoint,
176 touchInfo.adjustedRadius,
177 point.rotationAngle(),
178 point.force(),
179 touchInfo.region);
180
181 // Ensure this target's touch list exists, even if it ends up empty, so
182 // it can always be passed to TouchEvent::Create below.
183 TargetTouchesHeapMap::iterator targetTouchesIterator = touchesByTarget.f ind(touchInfo.touchNode.get());
184 if (targetTouchesIterator == touchesByTarget.end()) {
185 touchesByTarget.set(touchInfo.touchNode.get(), TouchList::create());
186 targetTouchesIterator = touchesByTarget.find(touchInfo.touchNode.get ());
187 }
188
189 // touches and targetTouches should only contain information about
190 // touches still on the screen, so if this point is released or
191 // cancelled it will only appear in the changedTouches list.
192 if (pointState != PlatformTouchPoint::TouchReleased && pointState != Pla tformTouchPoint::TouchCancelled) {
193 touches->append(touch);
194 targetTouchesIterator->value->append(touch);
195 }
196
197 // Now build up the correct list for changedTouches.
198 // Note that any touches that are in the TouchStationary state (e.g. if
199 // the user had several points touched but did not move them all) should
200 // never be in the changedTouches list so we do not handle them
201 // explicitly here. See https://bugs.webkit.org/show_bug.cgi?id=37609
202 // for further discussion about the TouchStationary state.
203 if (pointState != PlatformTouchPoint::TouchStationary && touchInfo.known Target) {
204 ASSERT(pointState < PlatformTouchPoint::TouchStateEnd);
205 if (!changedTouches[pointState].m_touches)
206 changedTouches[pointState].m_touches = TouchList::create();
207 changedTouches[pointState].m_touches->append(touch);
208 changedTouches[pointState].m_targets.add(touchInfo.touchNode);
209 }
210 }
211 if (allTouchReleased) {
212 m_touchSequenceDocument.clear();
213 m_touchSequenceUserGestureToken.clear();
214 }
215
216 WebInputEventResult eventResult = WebInputEventResult::NotHandled;
217
218 // Now iterate through the changedTouches list and m_targets within it, send ing
219 // TouchEvents to the targets as required.
220 for (unsigned state = 0; state != PlatformTouchPoint::TouchStateEnd; ++state ) {
221 if (!changedTouches[state].m_touches)
222 continue;
223
224 const AtomicString& eventName(touchEventNameForTouchPointState(static_ca st<PlatformTouchPoint::TouchState>(state)));
225 for (const auto& eventTarget : changedTouches[state].m_targets) {
226 EventTarget* touchEventTarget = eventTarget;
227 TouchEvent* touchEvent = TouchEvent::create(
228 touches, touchesByTarget.get(touchEventTarget), changedTouches[s tate].m_touches.get(),
229 eventName, touchEventTarget->toNode()->document().domWindow(),
230 event.getModifiers(), event.cancelable(), event.causesScrollingI fUncanceled(), event.timestamp());
231
232 DispatchEventResult domDispatchResult = touchEventTarget->dispatchEv ent(touchEvent);
233
234 // Only report for top level documents with a single touch on
235 // touch-start or the first touch-move.
236 if (touchStartOrFirstTouchMove && touchInfos.size() == 1 && event.ca ncelable() && !m_frame->document()->ownerElement()) {
237 DEFINE_STATIC_LOCAL(EnumerationHistogram, rootDocumentListenerHi stogram, ("Event.Touch.TargetAndDispatchResult", TouchTargetAndDispatchResultTyp eMax));
238 rootDocumentListenerHistogram.count(toTouchTargetHistogramValue( eventTarget, domDispatchResult));
239 }
240 eventResult = EventHandler::mergeEventResult(eventResult,
241 EventHandler::toWebInputEventResult(domDispatchResult));
242 }
243 }
244
245 return eventResult;
246 }
247
248 void TouchEventManager::updateTargetAndRegionMapsForTouchStarts(
249 HeapVector<TouchInfo>& touchInfos)
250 {
251 for (auto& touchInfo : touchInfos) {
252 // Touch events implicitly capture to the touched node, and don't change
253 // active/hover states themselves (Gesture events do). So we only need
254 // to hit-test on touchstart and when the target could be different than
255 // the corresponding pointer event target.
256 if (touchInfo.point.state() == PlatformTouchPoint::TouchPressed) {
257 if (m_touchSequenceDocument) {
258 if (!m_touchSequenceDocument->frame())
259 continue;
260 if (!touchInfo.touchNode || m_touchSequenceDocument.get()
261 != touchInfo.touchNode->document()) {
262 HitTestRequest::HitTestRequestType hitType = HitTestRequest: :TouchEvent | HitTestRequest::ReadOnly | HitTestRequest::Active;
263 // Hittest this touch point again as its original hittest fr ame is different from the first touched frame.
264 LayoutPoint framePoint = roundedLayoutPoint(m_touchSequenceD ocument->frame()->view()->rootFrameToContents(touchInfo.point.pos()));
265 HitTestResult result = EventHandler::hitTestResultInFrame(m_ touchSequenceDocument->frame(), framePoint, hitType);
266 Node* node = result.innerNode();
267 if (!node)
268 continue;
269 if (isHTMLCanvasElement(node)) {
270 std::pair<Element*, String> regionInfo = toHTMLCanvasEle ment(node)->getControlAndIdIfHitRegionExists(result.pointInInnerNodeFrame());
271 if (regionInfo.first)
272 node = regionInfo.first;
273 touchInfo.region = regionInfo.second;
274 }
275 // Touch events should not go to text nodes.
276 if (node->isTextNode())
277 node = FlatTreeTraversal::parent(*node);
278 touchInfo.touchNode = node;
279 }
280 }
281
282 if (!touchInfo.touchNode)
283 continue;
284
285 if (!m_touchSequenceDocument) {
286 // Keep track of which document should receive all touch events
287 // in the active sequence. This must be a single document to
288 // ensure we don't leak Nodes between documents.
289 m_touchSequenceDocument = &(touchInfo.touchNode->document());
290 ASSERT(m_touchSequenceDocument->frame()->view());
291 }
292
293 // Ideally we'd ASSERT(!m_targetForTouchID.contains(point.id())
294 // since we shouldn't get a touchstart for a touch that's already
295 // down. However EventSender allows this to be violated and there's
296 // some tests that take advantage of it. There may also be edge
297 // cases in the browser where this happens.
298 // See http://crbug.com/345372.
299 m_targetForTouchID.set(touchInfo.point.id(), touchInfo.touchNode);
300
301 m_regionForTouchID.set(touchInfo.point.id(), touchInfo.region);
302
303 TouchAction effectiveTouchAction =
304 TouchActionUtil::computeEffectiveTouchAction(
305 *touchInfo.touchNode);
306 if (effectiveTouchAction != TouchActionAuto)
307 m_frame->page()->chromeClient().setTouchAction(effectiveTouchAct ion);
308 }
309 }
310 }
311
312 void TouchEventManager::setAllPropertiesOfTouchInfos(
313 HeapVector<TouchInfo>& touchInfos)
314 {
315 for (auto& touchInfo : touchInfos) {
316 PlatformTouchPoint::TouchState pointState = touchInfo.point.state();
317 Node* touchNode = nullptr;
318 String regionID;
319
320 if (pointState == PlatformTouchPoint::TouchReleased
321 || pointState == PlatformTouchPoint::TouchCancelled) {
322 // The target should be the original target for this touch, so get
323 // it from the hashmap. As it's a release or cancel we also remove
324 // it from the map.
325 touchNode = m_targetForTouchID.take(touchInfo.point.id());
326 regionID = m_regionForTouchID.take(touchInfo.point.id());
327 } else {
328 // No hittest is performed on move or stationary, since the target
329 // is not allowed to change anyway.
330 touchNode = m_targetForTouchID.get(touchInfo.point.id());
331 regionID = m_regionForTouchID.get(touchInfo.point.id());
332 }
333
334 LocalFrame* targetFrame = nullptr;
335 bool knownTarget = false;
336 if (touchNode) {
337 Document& doc = touchNode->document();
338 // If the target node has moved to a new document while it was being touched,
339 // we can't send events to the new document because that could leak nodes
340 // from one document to another. See http://crbug.com/394339.
341 if (&doc == m_touchSequenceDocument.get()) {
342 targetFrame = doc.frame();
343 knownTarget = true;
344 }
345 }
346 if (!knownTarget) {
347 // If we don't have a target registered for the point it means we've
348 // missed our opportunity to do a hit test for it (due to some
349 // optimization that prevented blink from ever seeing the
350 // touchstart), or that the touch started outside the active touch
351 // sequence document. We should still include the touch in the
352 // Touches list reported to the application (eg. so it can
353 // differentiate between a one and two finger gesture), but we won't
354 // actually dispatch any events for it. Set the target to the
355 // Document so that there's some valid node here. Perhaps this
356 // should really be LocalDOMWindow, but in all other cases the targe t of
357 // a Touch is a Node so using the window could be a breaking change.
358 // Since we know there was no handler invoked, the specific target
359 // should be completely irrelevant to the application.
360 touchNode = m_touchSequenceDocument;
361 targetFrame = m_touchSequenceDocument->frame();
362 }
363 ASSERT(targetFrame);
364
365 // pagePoint should always be in the target element's document coordinat es.
366 FloatPoint pagePoint = targetFrame->view()->rootFrameToContents(
367 touchInfo.point.pos());
368 float scaleFactor = 1.0f / targetFrame->pageZoomFactor();
369
370 touchInfo.touchNode = touchNode;
371 touchInfo.targetFrame = targetFrame;
372 touchInfo.adjustedPagePoint = pagePoint.scaledBy(scaleFactor);
373 touchInfo.adjustedRadius = touchInfo.point.radius().scaledBy(scaleFactor );
374 touchInfo.knownTarget = knownTarget;
375 touchInfo.region = regionID;
376 }
377 }
378
379 WebInputEventResult TouchEventManager::handleTouchEvent(
380 const PlatformTouchEvent& event,
381 HeapVector<TouchInfo>& touchInfos)
382 {
383 bool newTouchSequence = true;
384 bool allTouchReleased = true;
385
386 for (const auto& point : event.touchPoints()) {
387 if (point.state() != PlatformTouchPoint::TouchPressed)
388 newTouchSequence = false;
389 if (point.state() != PlatformTouchPoint::TouchReleased && point.state() != PlatformTouchPoint::TouchCancelled)
390 allTouchReleased = false;
391 }
392 if (newTouchSequence) {
393 // Ideally we'd ASSERT !m_touchSequenceDocument here since we should
394 // have cleared the active document when we saw the last release. But we
395 // have some tests that violate this, ClusterFuzz could trigger it, and
396 // there may be cases where the browser doesn't reliably release all
397 // touches. http://crbug.com/345372 tracks this.
398 m_touchSequenceDocument.clear();
399 m_touchSequenceUserGestureToken.clear();
400 }
401
402 ASSERT(m_frame->view());
403 if (m_touchSequenceDocument && (!m_touchSequenceDocument->frame() || !m_touc hSequenceDocument->frame()->view())) {
404 // If the active touch document has no frame or view, it's probably bein g destroyed
405 // so we can't dispatch events.
406 return WebInputEventResult::NotHandled;
407 }
408
409 updateTargetAndRegionMapsForTouchStarts(touchInfos);
410
411 m_touchPressed = !allTouchReleased;
412
413 // If there's no document receiving touch events, or no handlers on the
414 // document set to receive the events, then we can skip all the rest of
415 // this work.
416 if (!m_touchSequenceDocument || !m_touchSequenceDocument->frameHost() || !ha sTouchHandlers(m_touchSequenceDocument->frameHost()->eventHandlerRegistry()) || !m_touchSequenceDocument->frame()) {
417 if (allTouchReleased) {
418 m_touchSequenceDocument.clear();
419 m_touchSequenceUserGestureToken.clear();
420 }
421 return WebInputEventResult::NotHandled;
422 }
423
424 // Whether a touch should be considered a "user gesture" or not is a tricky question.
425 // https://docs.google.com/document/d/1oF1T3O7_E4t1PYHV6gyCwHxOi3ystm0eSL5xZ u7nvOg/edit#
426 // TODO(rbyers): Disable user gesture in some cases but retain logging for n ow (crbug.com/582140).
427 OwnPtr<UserGestureIndicator> gestureIndicator;
428 if (event.touchPoints().size() == 1
429 && event.touchPoints()[0].state() == PlatformTouchPoint::TouchReleased
430 && !event.causesScrollingIfUncanceled()) {
431 // This is a touchend corresponding to a tap, definitely a user gesture. So don't supply
432 // a UserGestureUtilizedCallback.
433 gestureIndicator = adoptPtr(new UserGestureIndicator(DefinitelyProcessin gUserGesture));
434 } else {
435 // This is some other touch event that perhaps shouldn't be considered a user gesture. So
436 // use a UserGestureUtilizedCallback to get metrics / deprecation warnin gs.
437 if (m_touchSequenceUserGestureToken)
438 gestureIndicator = adoptPtr(new UserGestureIndicator(m_touchSequence UserGestureToken.release(), &m_touchSequenceDocument->frame()->eventHandler()));
439 else
440 gestureIndicator = adoptPtr(new UserGestureIndicator(DefinitelyProce ssingUserGesture, &m_touchSequenceDocument->frame()->eventHandler()));
441 m_touchSequenceUserGestureToken = UserGestureIndicator::currentToken();
442 }
443
444 setAllPropertiesOfTouchInfos(touchInfos);
445
446 // Note that the disposition of any pointer events affects only the generati on of touch
447 // events. If all pointer events were handled (and hence no touch events wer e fired), that
448 // is still equivalent to the touch events going unhandled because pointer e vent handler
449 // don't block scroll gesture generation.
450
451 // TODO(crbug.com/507408): If PE handlers always call preventDefault, we won 't see TEs until after
452 // scrolling starts because the scrolling would suppress upcoming PEs. This sudden "break" in TE
453 // suppression can make the visible TEs inconsistent (e.g. touchmove without a touchstart).
454
455 return dispatchTouchEvents(event, touchInfos, allTouchReleased);
456 }
457
458 void TouchEventManager::clear()
459 {
460 m_touchSequenceDocument.clear();
461 m_touchSequenceUserGestureToken.clear();
462 m_targetForTouchID.clear();
463 m_regionForTouchID.clear();
464 m_touchPressed = false;
465 m_waitingForFirstTouchMove = false;
466 }
467
468 bool TouchEventManager::isAnyTouchActive()
469 {
470 return m_touchPressed;
471 }
472
473 DEFINE_TRACE(TouchEventManager)
474 {
475 visitor->trace(m_frame);
476 visitor->trace(m_touchSequenceDocument);
477 visitor->trace(m_targetForTouchID);
478 }
479
480 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698