| Index: Source/core/page/EventHandler.cpp
|
| diff --git a/Source/core/page/EventHandler.cpp b/Source/core/page/EventHandler.cpp
|
| index fe0fcb17693312a9b493ac1bc5c384a54ad16bae..049bb7760068f1ecaeafac6f4af82f4e9a6a5d16 100644
|
| --- a/Source/core/page/EventHandler.cpp
|
| +++ b/Source/core/page/EventHandler.cpp
|
| @@ -222,7 +222,6 @@ EventHandler::EventHandler(LocalFrame* frame)
|
| , m_mousePositionIsUnknown(true)
|
| , m_mouseDownTimestamp(0)
|
| , m_widgetIsLatched(false)
|
| - , m_originatingTouchPointTargetKey(0)
|
| , m_touchPressed(false)
|
| , m_scrollGestureHandlingNode(nullptr)
|
| , m_lastHitTestResultOverWidget(false)
|
| @@ -278,9 +277,8 @@ void EventHandler::clear()
|
| m_capturingMouseEventsNode = nullptr;
|
| m_latchedWheelEventNode = nullptr;
|
| m_previousWheelScrolledNode = nullptr;
|
| - m_originatingTouchPointTargets.clear();
|
| - m_originatingTouchPointDocument.clear();
|
| - m_originatingTouchPointTargetKey = 0;
|
| + m_targetForTouchID.clear();
|
| + m_touchSequenceDocument.clear();
|
| m_scrollGestureHandlingNode = nullptr;
|
| m_lastHitTestResultOverWidget = false;
|
| m_previousGestureScrolledNode = nullptr;
|
| @@ -3437,27 +3435,6 @@ bool EventHandler::handleTouchEvent(const PlatformTouchEvent& event)
|
| {
|
| TRACE_EVENT0("webkit", "EventHandler::handleTouchEvent");
|
|
|
| - // First build up the lists to use for the 'touches', 'targetTouches' and 'changedTouches' attributes
|
| - // in the JS event. See http://www.sitepen.com/blog/2008/07/10/touching-and-gesturing-on-the-iphone/
|
| - // for an overview of how these lists fit together.
|
| -
|
| - // Holds the complete set of touches on the screen and will be used as the 'touches' list in the JS event.
|
| - RefPtrWillBeRawPtr<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.
|
| - typedef WillBeHeapHashMap<EventTarget*, RefPtrWillBeMember<TouchList> > TargetTouchesHeapMap;
|
| - TargetTouchesHeapMap touchesByTarget;
|
| -
|
| - // Array of touches per state, used to assemble the 'changedTouches' list in the JS event.
|
| - typedef HashSet<RefPtr<EventTarget> > EventTargetSet;
|
| - struct {
|
| - // The touches corresponding to the particular change state this struct instance represents.
|
| - RefPtrWillBeMember<TouchList> m_touches;
|
| - // Set of targets involved in m_touches.
|
| - EventTargetSet m_targets;
|
| - } changedTouches[PlatformTouchPoint::TouchStateEnd];
|
| -
|
| const Vector<PlatformTouchPoint>& points = event.touchPoints();
|
|
|
| UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture);
|
| @@ -3472,50 +3449,31 @@ bool EventHandler::handleTouchEvent(const PlatformTouchEvent& event)
|
| if (point.state() != PlatformTouchPoint::TouchReleased && point.state() != PlatformTouchPoint::TouchCancelled)
|
| allTouchReleased = false;
|
| }
|
| + if (freshTouchEvents) {
|
| + // Ideally we'd ASSERT !m_touchSequenceDocument here since we should
|
| + // have cleared the active document when we saw the last release. But we
|
| + // have some tests that violate this, ClusterFuzz could trigger it, and
|
| + // there may be cases where the browser doesn't reliably release all
|
| + // touches. http://crbug.com/345372 tracks this.
|
| + m_touchSequenceDocument.clear();
|
| + }
|
|
|
| + // First do hit tests for any new touch points.
|
| for (i = 0; i < points.size(); ++i) {
|
| const PlatformTouchPoint& point = points[i];
|
| - PlatformTouchPoint::State pointState = point.state();
|
| LayoutPoint pagePoint = documentPointForWindowPoint(m_frame, point.pos());
|
|
|
| - // Gesture events trigger the active state, not touch events,
|
| - // so touch event hit tests can always be read only.
|
| - HitTestRequest::HitTestRequestType hitType = HitTestRequest::TouchEvent | HitTestRequest::ReadOnly;
|
| - // The HitTestRequest types used for mouse events map quite adequately
|
| - // to touch events. Note that in addition to meaning that the hit test
|
| - // should affect the active state of the current node if necessary,
|
| - // HitTestRequest::Active signifies that the hit test is taking place
|
| - // with the mouse (or finger in this case) being pressed.
|
| - switch (pointState) {
|
| - case PlatformTouchPoint::TouchPressed:
|
| - hitType |= HitTestRequest::Active;
|
| - break;
|
| - case PlatformTouchPoint::TouchMoved:
|
| - hitType |= HitTestRequest::Active | HitTestRequest::Move;
|
| - break;
|
| - case PlatformTouchPoint::TouchReleased:
|
| - case PlatformTouchPoint::TouchCancelled:
|
| - hitType |= HitTestRequest::Release;
|
| - break;
|
| - case PlatformTouchPoint::TouchStationary:
|
| - hitType |= HitTestRequest::Active;
|
| - break;
|
| - default:
|
| - ASSERT_NOT_REACHED();
|
| - break;
|
| - }
|
| -
|
| - // Increment the platform touch id by 1 to avoid storing a key of 0 in the hashmap.
|
| - unsigned touchPointTargetKey = point.id() + 1;
|
| - RefPtr<EventTarget> touchTarget;
|
| - if (pointState == PlatformTouchPoint::TouchPressed) {
|
| + // Touch events implicitly capture to the touched node, and don't change
|
| + // active/hover states themselves (Gesture events do). So we only need
|
| + // to hit-test on touchstart, and it can be read-only.
|
| + if (point.state() == PlatformTouchPoint::TouchPressed) {
|
| + HitTestRequest::HitTestRequestType hitType = HitTestRequest::TouchEvent | HitTestRequest::ReadOnly | HitTestRequest::Active;
|
| HitTestResult result;
|
| - if (freshTouchEvents) {
|
| + if (!m_touchSequenceDocument) {
|
| result = hitTestResultAtPoint(pagePoint, hitType);
|
| - m_originatingTouchPointTargetKey = touchPointTargetKey;
|
| - } else if (m_originatingTouchPointDocument.get() && m_originatingTouchPointDocument->frame()) {
|
| - LayoutPoint pagePointInOriginatingDocument = documentPointForWindowPoint(m_originatingTouchPointDocument->frame(), point.pos());
|
| - result = hitTestResultInFrame(m_originatingTouchPointDocument->frame(), pagePointInOriginatingDocument, hitType);
|
| + } else if (m_touchSequenceDocument->frame()) {
|
| + LayoutPoint pagePointInOriginatingDocument = documentPointForWindowPoint(m_touchSequenceDocument->frame(), point.pos());
|
| + result = hitTestResultInFrame(m_touchSequenceDocument->frame(), pagePointInOriginatingDocument, hitType);
|
| } else
|
| continue;
|
|
|
| @@ -3527,40 +3485,108 @@ bool EventHandler::handleTouchEvent(const PlatformTouchEvent& event)
|
| if (node->isTextNode())
|
| node = NodeRenderingTraversal::parent(node);
|
|
|
| - Document& doc = node->document();
|
| - // Record the originating touch document even if it does not have a touch listener.
|
| - if (freshTouchEvents) {
|
| - m_originatingTouchPointDocument = &doc;
|
| - freshTouchEvents = false;
|
| + if (!m_touchSequenceDocument) {
|
| + // Keep track of which document should receive all touch events
|
| + // in the active sequence. This must be a single document to
|
| + // ensure we don't leak Nodes between documents.
|
| + m_touchSequenceDocument = &(result.innerNode()->document());
|
| }
|
| - if (!doc.hasTouchEventHandlers())
|
| - continue;
|
| - m_originatingTouchPointTargets.set(touchPointTargetKey, node);
|
| - touchTarget = node;
|
| +
|
| + // Ideally we'd ASSERT(!m_targetForTouchID.contains(point.id())
|
| + // since we shouldn't get a touchstart for a touch that's already
|
| + // down. However EventSender allows this to be violated and there's
|
| + // some tests that take advantage of it. There may also be edge
|
| + // cases in the browser where this happens.
|
| + // See http://crbug.com/345372.
|
| + m_targetForTouchID.set(point.id(), node);
|
|
|
| TouchAction effectiveTouchAction = computeEffectiveTouchAction(pagePoint);
|
| if (effectiveTouchAction != TouchActionAuto)
|
| m_frame->page()->chrome().client().setTouchAction(effectiveTouchAction);
|
| + }
|
| + }
|
|
|
| - } else if (pointState == PlatformTouchPoint::TouchReleased || pointState == PlatformTouchPoint::TouchCancelled) {
|
| - // The target should be the original target for this touch, so get it from the hashmap. As it's a release or cancel
|
| - // we also remove it from the map.
|
| - touchTarget = m_originatingTouchPointTargets.take(touchPointTargetKey);
|
| - } else
|
| - // No hittest is performed on move or stationary, since the target is not allowed to change anyway.
|
| - touchTarget = m_originatingTouchPointTargets.get(touchPointTargetKey);
|
| + m_touchPressed = !allTouchReleased;
|
|
|
| - if (!touchTarget.get())
|
| - continue;
|
| - Document& doc = touchTarget->toNode()->document();
|
| - if (!doc.hasTouchEventHandlers())
|
| - continue;
|
| - LocalFrame* targetFrame = doc.frame();
|
| - if (!targetFrame)
|
| - continue;
|
| + // If there's no document receiving touch events, or no handlers on the
|
| + // document set to receive the events, then we can skip all the rest of
|
| + // this work.
|
| + if (!m_touchSequenceDocument || !m_touchSequenceDocument->hasTouchEventHandlers() || !m_touchSequenceDocument->frame()) {
|
| + if (allTouchReleased)
|
| + m_touchSequenceDocument.clear();
|
| + return 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.
|
| + RefPtrWillBeRawPtr<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.
|
| + typedef WillBeHeapHashMap<EventTarget*, RefPtrWillBeMember<TouchList> > TargetTouchesHeapMap;
|
| + TargetTouchesHeapMap touchesByTarget;
|
| +
|
| + // Array of touches per state, used to assemble the 'changedTouches' list.
|
| + typedef HashSet<RefPtr<EventTarget> > EventTargetSet;
|
| + struct {
|
| + // The touches corresponding to the particular change state this struct
|
| + // instance represents.
|
| + RefPtrWillBeMember<TouchList> m_touches;
|
| + // Set of targets involved in m_touches.
|
| + EventTargetSet m_targets;
|
| + } changedTouches[PlatformTouchPoint::TouchStateEnd];
|
| +
|
| + for (i = 0; i < points.size(); ++i) {
|
| + const PlatformTouchPoint& point = points[i];
|
| + LayoutPoint pagePoint = documentPointForWindowPoint(m_frame, point.pos());
|
| + PlatformTouchPoint::State pointState = point.state();
|
| + RefPtr<EventTarget> touchTarget;
|
| +
|
| + if (pointState == PlatformTouchPoint::TouchReleased || pointState == PlatformTouchPoint::TouchCancelled) {
|
| + // The target should be the original target for this touch, so get
|
| + // it from the hashmap. As it's a release or cancel we also remove
|
| + // it from the map.
|
| + touchTarget = m_targetForTouchID.take(point.id());
|
| + } else {
|
| + // No hittest is performed on move or stationary, since the target
|
| + // is not allowed to change anyway.
|
| + touchTarget = m_targetForTouchID.get(point.id());
|
| + }
|
| +
|
| + LocalFrame* targetFrame;
|
| + bool knownTarget;
|
| + if (touchTarget) {
|
| + Document& doc = touchTarget->toNode()->document();
|
| + ASSERT(&doc == m_touchSequenceDocument.get());
|
| + targetFrame = doc.frame();
|
| + knownTarget = true;
|
| + } else {
|
| + // If we don't have a target registered for the point it means we've
|
| + // missed our opportunity to do a hit test for it (due to some
|
| + // optimization that prevented blink from ever seeing the
|
| + // touchstart), or that the touch started outside the active touch
|
| + // sequence document. We should still include the touch in the
|
| + // Touches list reported to the application (eg. so it can
|
| + // differentiate between a one and two finger gesture), but we won't
|
| + // actually dispatch any events for it. Set the target to the
|
| + // Document so that there's some valid node here. Perhaps this
|
| + // should really be DOMWindow, but in all other cases the target of
|
| + // a Touch is a Node so using the window could be a breaking change.
|
| + // Since we know there was no handler invoked, the specific target
|
| + // should be completely irrelevant to the application.
|
| + touchTarget = m_touchSequenceDocument;
|
| + targetFrame = m_touchSequenceDocument->frame();
|
| + knownTarget = false;
|
| + }
|
| + ASSERT(targetFrame);
|
|
|
| if (m_frame != targetFrame) {
|
| - // pagePoint should always be relative to the target elements containing frame.
|
| + // pagePoint should always be relative to the target elements
|
| + // containing frame.
|
| pagePoint = documentPointForWindowPoint(targetFrame, point.pos());
|
| }
|
|
|
| @@ -3577,15 +3603,17 @@ bool EventHandler::handleTouchEvent(const PlatformTouchEvent& event)
|
| adjustedRadiusX, adjustedRadiusY,
|
| point.rotationAngle(), point.force());
|
|
|
| - // Ensure this target's touch list exists, even if it ends up empty, so it can always be passed to TouchEvent::Create below.
|
| + // 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(touchTarget.get());
|
| if (targetTouchesIterator == touchesByTarget.end()) {
|
| touchesByTarget.set(touchTarget.get(), TouchList::create());
|
| targetTouchesIterator = touchesByTarget.find(touchTarget.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.
|
| + // 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);
|
| @@ -3594,10 +3622,10 @@ bool EventHandler::handleTouchEvent(const PlatformTouchEvent& event)
|
| // 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) {
|
| + // 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 && knownTarget) {
|
| ASSERT(pointState < PlatformTouchPoint::TouchStateEnd);
|
| if (!changedTouches[pointState].m_touches)
|
| changedTouches[pointState].m_touches = TouchList::create();
|
| @@ -3605,32 +3633,25 @@ bool EventHandler::handleTouchEvent(const PlatformTouchEvent& event)
|
| changedTouches[pointState].m_targets.add(touchTarget);
|
| }
|
| }
|
| - m_touchPressed = touches->length() > 0;
|
| if (allTouchReleased)
|
| - m_originatingTouchPointDocument.clear();
|
| + m_touchSequenceDocument.clear();
|
|
|
| - // Now iterate the changedTouches list and m_targets within it, sending events to the targets as required.
|
| + // Now iterate the changedTouches list and m_targets within it, sending
|
| + // events to the targets as required.
|
| bool swallowedEvent = false;
|
| RefPtrWillBeRawPtr<TouchList> emptyList = TouchList::create();
|
| for (unsigned state = 0; state != PlatformTouchPoint::TouchStateEnd; ++state) {
|
| if (!changedTouches[state].m_touches)
|
| continue;
|
|
|
| - // When sending a touch cancel event, use empty touches and targetTouches lists.
|
| - bool isTouchCancelEvent = (state == PlatformTouchPoint::TouchCancelled);
|
| - RefPtrWillBeRawPtr<TouchList>& effectiveTouches(isTouchCancelEvent ? emptyList : touches);
|
| const AtomicString& stateName(eventNameForTouchPointState(static_cast<PlatformTouchPoint::State>(state)));
|
| const EventTargetSet& targetsForState = changedTouches[state].m_targets;
|
| -
|
| for (EventTargetSet::const_iterator it = targetsForState.begin(); it != targetsForState.end(); ++it) {
|
| EventTarget* touchEventTarget = it->get();
|
| - RefPtrWillBeRawPtr<TouchList> targetTouches(isTouchCancelEvent ? emptyList.get() : touchesByTarget.get(touchEventTarget));
|
| - ASSERT(targetTouches);
|
| -
|
| - RefPtrWillBeRawPtr<TouchEvent> touchEvent =
|
| - TouchEvent::create(effectiveTouches.get(), targetTouches.get(), changedTouches[state].m_touches.get(),
|
| - stateName, touchEventTarget->toNode()->document().domWindow(),
|
| - 0, 0, 0, 0, event.ctrlKey(), event.altKey(), event.shiftKey(), event.metaKey(), event.cancelable());
|
| + RefPtrWillBeRawPtr<TouchEvent> touchEvent = TouchEvent::create(
|
| + touches.get(), touchesByTarget.get(touchEventTarget), changedTouches[state].m_touches.get(),
|
| + stateName, touchEventTarget->toNode()->document().domWindow(),
|
| + 0, 0, 0, 0, event.ctrlKey(), event.altKey(), event.shiftKey(), event.metaKey(), event.cancelable());
|
| touchEventTarget->toNode()->dispatchTouchEvent(touchEvent.get());
|
| swallowedEvent = swallowedEvent || touchEvent->defaultPrevented() || touchEvent->defaultHandled();
|
| }
|
|
|