WebInputEventResult TouchEventManager::handleTouchEvent( const PlatformTouchEvent& event, const HeapVector<TouchInfo>& touchInfos) { // Note that the disposition of any pointer events affects only the generation of touch // events. If all pointer events were handled (and hence no touch events were fired), that // is still equivalent to the touch events going unhandled because pointer event handler // don't block scroll gesture generation. // TODO(crbug.com/507408): If PE handlers always call preventDefault, we won't see TEs until after // scrolling starts because the scrolling would suppress upcoming PEs. This sudden "break" in TE // suppression can make the visible TEs inconsistent (e.g. touchmove without a touchstart). bool allTouchesReleased = true; for (const auto& point : event.touchPoints()) { if (point.state() != PlatformTouchPoint::TouchReleased && point.state() != PlatformTouchPoint::TouchCancelled) allTouchesReleased = false; } // Whether a touch should be considered a "user gesture" or not is a tricky question. // https://docs.google.com/document/d/1oF1T3O7_E4t1PYHV6gyCwHxOi3ystm0eSL5xZu7nvOg/edit# // The touchend corresponding to a tap is always a user gesture. bool isTap = event.touchPoints().size() == 1 && event.touchPoints()[0].state() == PlatformTouchPoint::TouchReleased && !event.causesScrollingIfUncanceled(); // For now, disallow dragging as a user gesture when the events are being sent to a // cross-origin iframe (crbug.com/582140). bool isSameOrigin = false; if (m_touchSequenceDocument && m_touchSequenceDocument->frame()) { SecurityOrigin* securityOrigin = m_touchSequenceDocument->frame()->securityContext()->getSecurityOrigin(); Frame* top = m_frame->tree().top(); if (top && securityOrigin->canAccess(top->securityContext()->getSecurityOrigin())) isSameOrigin = true; } OwnPtr<UserGestureIndicator> gestureIndicator; if (isTap || isSameOrigin) { UserGestureUtilizedCallback* callback = 0; if (!isTap) { // This is some other touch event that we currently consider a user gesture. So // use a UserGestureUtilizedCallback to get metrics. callback = &m_touchSequenceDocument->frame()->eventHandler(); } if (m_touchSequenceUserGestureToken) gestureIndicator = adoptPtr(new UserGestureIndicator(m_touchSequenceUserGestureToken.release(), callback)); else gestureIndicator = adoptPtr(new UserGestureIndicator(DefinitelyProcessingUserGesture, callback)); m_touchSequenceUserGestureToken = UserGestureIndicator::currentToken(); } return dispatchTouchEvents(event, touchInfos, allTouchesReleased); }
WebInputEventResult TouchEventManager::dispatchTouchEvents( const PlatformTouchEvent& event, const HeapVector<TouchInfo>& touchInfos, bool allTouchesReleased) { bool touchStartOrFirstTouchMove = false; if (event.type() == PlatformEvent::TouchStart) { m_waitingForFirstTouchMove = true; touchStartOrFirstTouchMove = true; } else if (event.type() == PlatformEvent::TouchMove) { touchStartOrFirstTouchMove = m_waitingForFirstTouchMove; m_waitingForFirstTouchMove = false; } // 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 (unsigned i = 0; i < touchInfos.size(); ++i) { const TouchInfo& touchInfo = touchInfos[i]; const PlatformTouchPoint& point = touchInfo.point; PlatformTouchPoint::TouchState pointState = point.state(); if (touchInfo.consumed) continue; 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); } } if (allTouchesReleased) { m_touchSequenceDocument.clear(); m_touchSequenceUserGestureToken.clear(); } 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.timestamp()); DispatchEventResult domDispatchResult = touchEventTarget->dispatchEvent(touchEvent); // Only report for top level documents with a single touch on // touch-start or the first touch-move. if (touchStartOrFirstTouchMove && touchInfos.size() == 1 && event.cancelable() && m_frame->isMainFrame()) { DEFINE_STATIC_LOCAL(EnumerationHistogram, rootDocumentListenerHistogram, ("Event.Touch.TargetAndDispatchResult", TouchTargetAndDispatchResultTypeMax)); rootDocumentListenerHistogram.count(toTouchTargetHistogramValue(eventTarget, domDispatchResult)); // Count the handled touch starts and first touch moves before and after the page is fully loaded respectively. if (m_frame->document()->isLoadCompleted()) { DEFINE_STATIC_LOCAL(EnumerationHistogram, touchDispositionsAfterPageLoadHistogram, ("Event.Touch.TouchDispositionsAfterPageLoad", TouchEventDispatchResultTypeMax)); touchDispositionsAfterPageLoadHistogram.count((domDispatchResult != DispatchEventResult::NotCanceled) ? HandledTouches : UnhandledTouches); } else { DEFINE_STATIC_LOCAL(EnumerationHistogram, touchDispositionsBeforePageLoadHistogram, ("Event.Touch.TouchDispositionsBeforePageLoad", TouchEventDispatchResultTypeMax)); touchDispositionsBeforePageLoadHistogram.count((domDispatchResult != DispatchEventResult::NotCanceled) ? HandledTouches : UnhandledTouches); } } eventResult = EventHandler::mergeEventResult(eventResult, EventHandler::toWebInputEventResult(domDispatchResult)); } } return eventResult; }