| OLD | NEW |
| (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 |
| OLD | NEW |