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

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, 8 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/PlatformTouchEvent.h"
18
19
20
21 namespace blink {
22
23 namespace {
24
25 const AtomicString& touchEventNameForTouchPointState(
26 PlatformTouchPoint::TouchState state)
27 {
28 switch (state) {
29 case PlatformTouchPoint::TouchReleased:
30 return EventTypeNames::touchend;
31 case PlatformTouchPoint::TouchCancelled:
32 return EventTypeNames::touchcancel;
33 case PlatformTouchPoint::TouchPressed:
34 return EventTypeNames::touchstart;
35 case PlatformTouchPoint::TouchMoved:
36 return EventTypeNames::touchmove;
37 case PlatformTouchPoint::TouchStationary:
38 // Fall through to default
39 default:
40 ASSERT_NOT_REACHED();
41 return emptyAtom;
42 }
43 }
44
45 bool hasTouchHandlers(const EventHandlerRegistry& registry)
46 {
47 return registry.hasEventHandlers(EventHandlerRegistry::TouchEventBlocking)
48 || registry.hasEventHandlers(EventHandlerRegistry::TouchEventPassive);
49 }
50
51 }
52
53 TouchEventManager::TouchEventManager(LocalFrame* frame)
54 : m_frame(frame)
55 {
56 clear();
57 }
58
59 TouchEventManager::~TouchEventManager()
60 {
61 }
62
63 namespace {
64
65 // Defining this class type local to dispatchTouchEvents() and annotating
66 // it with STACK_ALLOCATED(), runs into MSVC(VS 2013)'s C4822 warning
67 // that the local class doesn't provide a local definition for 'operator new'.
68 // Which it intentionally doesn't and shouldn't.
69 //
70 // Work around such toolchain bugginess by lifting out the type, thereby
71 // taking it out of C4822's reach.
72 class ChangedTouches final {
73 STACK_ALLOCATED();
74 public:
75 // The touches corresponding to the particular change state this struct
76 // instance represents.
77 Member<TouchList> m_touches;
78
79 using EventTargetSet = HeapHashSet<Member<EventTarget>>;
80 // Set of targets involved in m_touches.
81 EventTargetSet m_targets;
82 };
83
84 } // namespace
85
86 WebInputEventResult TouchEventManager::dispatchTouchEvents(
87 const PlatformTouchEvent& event,
88 HeapVector<TouchInfo>& touchInfos,
89 bool allTouchReleased)
90 {
91 // Build up the lists to use for the 'touches', 'targetTouches' and
92 // 'changedTouches' attributes in the JS event. See
93 // http://www.w3.org/TR/touch-events/#touchevent-interface for how these
94 // lists fit together.
95
96 // Holds the complete set of touches on the screen.
97 TouchList* touches = TouchList::create();
98
99 // A different view on the 'touches' list above, filtered and grouped by
100 // event target. Used for the 'targetTouches' list in the JS event.
101 using TargetTouchesHeapMap = HeapHashMap<EventTarget*, Member<TouchList>>;
102 TargetTouchesHeapMap touchesByTarget;
103
104 // Array of touches per state, used to assemble the 'changedTouches' list.
105 ChangedTouches changedTouches[PlatformTouchPoint::TouchStateEnd];
106
107 for (unsigned i = 0; i < touchInfos.size(); ++i) {
108 const TouchInfo& touchInfo = touchInfos[i];
109 const PlatformTouchPoint& point = touchInfo.point;
110 PlatformTouchPoint::TouchState pointState = point.state();
111
112 Touch* touch = Touch::create(
113 touchInfo.targetFrame.get(),
114 touchInfo.touchNode.get(),
115 point.id(),
116 point.screenPos(),
117 touchInfo.adjustedPagePoint,
118 touchInfo.adjustedRadius,
119 point.rotationAngle(),
120 point.force(),
121 touchInfo.region);
122
123 // Ensure this target's touch list exists, even if it ends up empty, so
124 // it can always be passed to TouchEvent::Create below.
125 TargetTouchesHeapMap::iterator targetTouchesIterator = touchesByTarget.f ind(touchInfo.touchNode.get());
126 if (targetTouchesIterator == touchesByTarget.end()) {
127 touchesByTarget.set(touchInfo.touchNode.get(), TouchList::create());
128 targetTouchesIterator = touchesByTarget.find(touchInfo.touchNode.get ());
129 }
130
131 // touches and targetTouches should only contain information about
132 // touches still on the screen, so if this point is released or
133 // cancelled it will only appear in the changedTouches list.
134 if (pointState != PlatformTouchPoint::TouchReleased && pointState != Pla tformTouchPoint::TouchCancelled) {
135 touches->append(touch);
136 targetTouchesIterator->value->append(touch);
137 }
138
139 // Now build up the correct list for changedTouches.
140 // Note that any touches that are in the TouchStationary state (e.g. if
141 // the user had several points touched but did not move them all) should
142 // never be in the changedTouches list so we do not handle them
143 // explicitly here. See https://bugs.webkit.org/show_bug.cgi?id=37609
144 // for further discussion about the TouchStationary state.
145 if (pointState != PlatformTouchPoint::TouchStationary && touchInfo.known Target) {
146 ASSERT(pointState < PlatformTouchPoint::TouchStateEnd);
147 if (!changedTouches[pointState].m_touches)
148 changedTouches[pointState].m_touches = TouchList::create();
149 changedTouches[pointState].m_touches->append(touch);
150 changedTouches[pointState].m_targets.add(touchInfo.touchNode);
151 }
152 }
153 if (allTouchReleased) {
154 m_touchSequenceDocument.clear();
155 m_touchSequenceUserGestureToken.clear();
156 }
157
158 WebInputEventResult eventResult = WebInputEventResult::NotHandled;
159
160 // Now iterate through the changedTouches list and m_targets within it, send ing
161 // TouchEvents to the targets as required.
162 for (unsigned state = 0; state != PlatformTouchPoint::TouchStateEnd; ++state ) {
163 if (!changedTouches[state].m_touches)
164 continue;
165
166 const AtomicString& eventName(touchEventNameForTouchPointState(static_ca st<PlatformTouchPoint::TouchState>(state)));
167 for (const auto& eventTarget : changedTouches[state].m_targets) {
168 EventTarget* touchEventTarget = eventTarget;
169 TouchEvent* touchEvent = TouchEvent::create(
170 touches, touchesByTarget.get(touchEventTarget), changedTouches[s tate].m_touches.get(),
171 eventName, touchEventTarget->toNode()->document().domWindow(),
172 event.getModifiers(), event.cancelable(), event.causesScrollingI fUncanceled(), event.timestamp());
173
174 eventResult = EventHandler::mergeEventResult(eventResult, EventHandl er::toWebInputEventResult(touchEventTarget->dispatchEvent(touchEvent)));
175 }
176 }
177
178 return eventResult;
179 }
180
181 void TouchEventManager::updateTargetAndRegionMapsForTouchStarts(
182 HeapVector<TouchInfo>& touchInfos)
183 {
184 for (auto& touchInfo : touchInfos) {
185 // Touch events implicitly capture to the touched node, and don't change
186 // active/hover states themselves (Gesture events do). So we only need
187 // to hit-test on touchstart and when the target could be different than
188 // the corresponding pointer event target.
189 if (touchInfo.point.state() == PlatformTouchPoint::TouchPressed) {
190 if (m_touchSequenceDocument) {
191 if (!m_touchSequenceDocument->frame())
192 continue;
193 if (!touchInfo.touchNode || m_touchSequenceDocument.get()
194 != touchInfo.touchNode->document()) {
195 HitTestRequest::HitTestRequestType hitType = HitTestRequest: :TouchEvent | HitTestRequest::ReadOnly | HitTestRequest::Active;
196 // Hittest this touch point again as its original hittest fr ame is different from the first touched frame.
197 LayoutPoint framePoint = roundedLayoutPoint(m_touchSequenceD ocument->frame()->view()->rootFrameToContents(touchInfo.point.pos()));
198 HitTestResult result = EventHandler::hitTestResultInFrame(m_ touchSequenceDocument->frame(), framePoint, hitType);
199 Node* node = result.innerNode();
200 if (!node)
201 continue;
202 if (isHTMLCanvasElement(node)) {
203 std::pair<Element*, String> regionInfo = toHTMLCanvasEle ment(node)->getControlAndIdIfHitRegionExists(result.pointInInnerNodeFrame());
204 if (regionInfo.first)
205 node = regionInfo.first;
206 touchInfo.region = regionInfo.second;
207 }
208 // Touch events should not go to text nodes.
209 if (node->isTextNode())
210 node = FlatTreeTraversal::parent(*node);
211 touchInfo.touchNode = node;
212 }
213 }
214
215 if (!touchInfo.touchNode)
216 continue;
217
218 if (!m_touchSequenceDocument) {
219 // Keep track of which document should receive all touch events
220 // in the active sequence. This must be a single document to
221 // ensure we don't leak Nodes between documents.
222 m_touchSequenceDocument = &(touchInfo.touchNode->document());
223 ASSERT(m_touchSequenceDocument->frame()->view());
224 }
225
226 // Ideally we'd ASSERT(!m_targetForTouchID.contains(point.id())
227 // since we shouldn't get a touchstart for a touch that's already
228 // down. However EventSender allows this to be violated and there's
229 // some tests that take advantage of it. There may also be edge
230 // cases in the browser where this happens.
231 // See http://crbug.com/345372.
232 m_targetForTouchID.set(touchInfo.point.id(), touchInfo.touchNode);
233
234 m_regionForTouchID.set(touchInfo.point.id(), touchInfo.region);
235
236 TouchAction effectiveTouchAction =
237 TouchActionUtil::computeEffectiveTouchAction(
238 *touchInfo.touchNode);
239 if (effectiveTouchAction != TouchActionAuto)
240 m_frame->page()->chromeClient().setTouchAction(effectiveTouchAct ion);
241 }
242 }
243 }
244
245 void TouchEventManager::setAllPropertiesofTouchInfos(
246 HeapVector<TouchInfo>& touchInfos)
247 {
248 for (auto& touchInfo : touchInfos) {
249 PlatformTouchPoint::TouchState pointState = touchInfo.point.state();
250 Node* touchTarget = nullptr;
251 String regionID;
252
253 if (pointState == PlatformTouchPoint::TouchReleased
254 || pointState == PlatformTouchPoint::TouchCancelled) {
255 // The target should be the original target for this touch, so get
256 // it from the hashmap. As it's a release or cancel we also remove
257 // it from the map.
258 touchTarget = m_targetForTouchID.take(touchInfo.point.id());
259 regionID = m_regionForTouchID.take(touchInfo.point.id());
260 } else {
261 // No hittest is performed on move or stationary, since the target
262 // is not allowed to change anyway.
263 touchTarget = m_targetForTouchID.get(touchInfo.point.id());
264 regionID = m_regionForTouchID.get(touchInfo.point.id());
265 }
266
267 LocalFrame* targetFrame = nullptr;
268 bool knownTarget = false;
269 if (touchTarget) {
270 Document& doc = touchTarget->toNode()->document();
271 // If the target node has moved to a new document while it was being touched,
272 // we can't send events to the new document because that could leak nodes
273 // from one document to another. See http://crbug.com/394339.
274 if (&doc == m_touchSequenceDocument.get()) {
275 targetFrame = doc.frame();
276 knownTarget = true;
277 }
278 }
279 if (!knownTarget) {
280 // If we don't have a target registered for the point it means we've
281 // missed our opportunity to do a hit test for it (due to some
282 // optimization that prevented blink from ever seeing the
283 // touchstart), or that the touch started outside the active touch
284 // sequence document. We should still include the touch in the
285 // Touches list reported to the application (eg. so it can
286 // differentiate between a one and two finger gesture), but we won't
287 // actually dispatch any events for it. Set the target to the
288 // Document so that there's some valid node here. Perhaps this
289 // should really be LocalDOMWindow, but in all other cases the targe t of
290 // a Touch is a Node so using the window could be a breaking change.
291 // Since we know there was no handler invoked, the specific target
292 // should be completely irrelevant to the application.
293 touchTarget = m_touchSequenceDocument;
294 targetFrame = m_touchSequenceDocument->frame();
295 }
296 ASSERT(targetFrame);
297
298 // pagePoint should always be in the target element's document coordinat es.
299 FloatPoint pagePoint = targetFrame->view()->rootFrameToContents(
300 touchInfo.point.pos());
301 float scaleFactor = 1.0f / targetFrame->pageZoomFactor();
302
303 touchInfo.touchNode = touchTarget;
304 touchInfo.targetFrame = targetFrame;
305 touchInfo.adjustedPagePoint = pagePoint.scaledBy(scaleFactor);
306 touchInfo.adjustedRadius = touchInfo.point.radius().scaledBy(scaleFactor );
307 touchInfo.knownTarget = knownTarget;
308 touchInfo.region = regionID;
309 }
310 }
311
312 WebInputEventResult TouchEventManager::handleTouchEvent(
313 const PlatformTouchEvent& event,
314 HeapVector<TouchInfo>& touchInfos)
315 {
316 bool newTouchSequence = true;
317 bool allTouchReleased = true;
318
319 for (const auto& point : event.touchPoints()) {
320 if (point.state() != PlatformTouchPoint::TouchPressed)
321 newTouchSequence = false;
322 if (point.state() != PlatformTouchPoint::TouchReleased && point.state() != PlatformTouchPoint::TouchCancelled)
323 allTouchReleased = false;
324 }
325 if (newTouchSequence) {
326 // Ideally we'd ASSERT !m_touchSequenceDocument here since we should
327 // have cleared the active document when we saw the last release. But we
328 // have some tests that violate this, ClusterFuzz could trigger it, and
329 // there may be cases where the browser doesn't reliably release all
330 // touches. http://crbug.com/345372 tracks this.
331 m_touchSequenceDocument.clear();
332 m_touchSequenceUserGestureToken.clear();
333 }
334
335 ASSERT(m_frame->view());
336 if (m_touchSequenceDocument && (!m_touchSequenceDocument->frame() || !m_touc hSequenceDocument->frame()->view())) {
337 // If the active touch document has no frame or view, it's probably bein g destroyed
338 // so we can't dispatch events.
339 return WebInputEventResult::NotHandled;
340 }
341
342 updateTargetAndRegionMapsForTouchStarts(touchInfos);
343
344 m_touchPressed = !allTouchReleased;
345
346 // If there's no document receiving touch events, or no handlers on the
347 // document set to receive the events, then we can skip all the rest of
348 // this work.
349 if (!m_touchSequenceDocument || !m_touchSequenceDocument->frameHost() || !ha sTouchHandlers(m_touchSequenceDocument->frameHost()->eventHandlerRegistry()) || !m_touchSequenceDocument->frame()) {
350 if (allTouchReleased) {
351 m_touchSequenceDocument.clear();
352 m_touchSequenceUserGestureToken.clear();
353 }
354 return WebInputEventResult::NotHandled;
355 }
356
357 // Whether a touch should be considered a "user gesture" or not is a tricky question.
358 // https://docs.google.com/document/d/1oF1T3O7_E4t1PYHV6gyCwHxOi3ystm0eSL5xZ u7nvOg/edit#
359 // TODO(rbyers): Disable user gesture in some cases but retain logging for n ow (crbug.com/582140).
360 OwnPtr<UserGestureIndicator> gestureIndicator;
361 if (event.touchPoints().size() == 1
362 && event.touchPoints()[0].state() == PlatformTouchPoint::TouchReleased
363 && !event.causesScrollingIfUncanceled()) {
364 // This is a touchend corresponding to a tap, definitely a user gesture. So don't supply
365 // a UserGestureUtilizedCallback.
366 gestureIndicator = adoptPtr(new UserGestureIndicator(DefinitelyProcessin gUserGesture));
367 } else {
368 // This is some other touch event that perhaps shouldn't be considered a user gesture. So
369 // use a UserGestureUtilizedCallback to get metrics / deprecation warnin gs.
370 if (m_touchSequenceUserGestureToken)
371 gestureIndicator = adoptPtr(new UserGestureIndicator(m_touchSequence UserGestureToken.release(), &m_touchSequenceDocument->frame()->eventHandler()));
372 else
373 gestureIndicator = adoptPtr(new UserGestureIndicator(DefinitelyProce ssingUserGesture, &m_touchSequenceDocument->frame()->eventHandler()));
374 m_touchSequenceUserGestureToken = UserGestureIndicator::currentToken();
375 }
376
377 setAllPropertiesofTouchInfos(touchInfos);
378
379 // Note that the disposition of any pointer events affects only the generati on of touch
380 // events. If all pointer events were handled (and hence no touch events wer e fired), that
381 // is still equivalent to the touch events going unhandled because pointer e vent handler
382 // don't block scroll gesture generation.
383
384 // TODO(crbug.com/507408): If PE handlers always call preventDefault, we won 't see TEs until after
385 // scrolling starts because the scrolling would suppress upcoming PEs. This sudden "break" in TE
386 // suppression can make the visible TEs inconsistent (e.g. touchmove without a touchstart).
387
388 return dispatchTouchEvents(event, touchInfos, allTouchReleased);
389 }
390
391 void TouchEventManager::clear()
392 {
393 m_touchSequenceDocument.clear();
394 m_touchSequenceUserGestureToken.clear();
395 m_targetForTouchID.clear();
396 m_regionForTouchID.clear();
397 m_touchPressed = false;
398 }
399
400 bool TouchEventManager::isAnyTouchActive()
401 {
402 return m_touchPressed;
403 }
404
405 DEFINE_TRACE(TouchEventManager)
406 {
407 visitor->trace(m_frame);
408 visitor->trace(m_touchSequenceDocument);
409 visitor->trace(m_targetForTouchID);
410 visitor->trace(m_regionForTouchID);
411 }
412
413 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698