/* * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved. * Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org) * Copyright (C) 2012 Digia Plc. and/or its subsidiary(-ies) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "core/page/EventHandler.h" #include "bindings/core/v8/ExceptionStatePlaceholder.h" #include "core/HTMLNames.h" #include "core/dom/Document.h" #include "core/dom/DocumentMarkerController.h" #include "core/dom/NodeRenderingTraversal.h" #include "core/dom/TouchList.h" #include "core/dom/shadow/ShadowRoot.h" #include "core/editing/Editor.h" #include "core/editing/FrameSelection.h" #include "core/editing/TextIterator.h" #include "core/editing/htmlediting.h" #include "core/events/DOMWindowEventQueue.h" #include "core/events/EventPath.h" #include "core/events/KeyboardEvent.h" #include "core/events/MouseEvent.h" #include "core/events/TextEvent.h" #include "core/events/TouchEvent.h" #include "core/events/WheelEvent.h" #include "core/fetch/ImageResource.h" #include "core/frame/EventHandlerRegistry.h" #include "core/frame/FrameView.h" #include "core/frame/LocalFrame.h" #include "core/frame/Settings.h" #include "core/loader/FrameLoaderClient.h" #include "core/page/AutoscrollController.h" #include "core/page/Chrome.h" #include "core/page/ChromeClient.h" #include "core/page/EditorClient.h" #include "core/page/EventWithHitTestResults.h" #include "core/page/FocusController.h" #include "core/page/Page.h" #include "core/page/TouchAdjustment.h" #include "core/rendering/HitTestRequest.h" #include "core/rendering/HitTestResult.h" #include "core/rendering/RenderLayer.h" #include "core/rendering/RenderView.h" #include "core/rendering/style/RenderStyle.h" #include "platform/PlatformGestureEvent.h" #include "platform/PlatformKeyboardEvent.h" #include "platform/PlatformTouchEvent.h" #include "platform/PlatformWheelEvent.h" #include "platform/RuntimeEnabledFeatures.h" #include "platform/TraceEvent.h" #include "platform/WindowsKeyboardCodes.h" #include "platform/geometry/FloatPoint.h" #include "platform/graphics/Image.h" #include "platform/heap/Handle.h" #include "platform/scroll/ScrollAnimator.h" #include "platform/scroll/Scrollbar.h" #include "wtf/Assertions.h" #include "wtf/CurrentTime.h" #include "wtf/StdLibExtras.h" #include "wtf/TemporaryChange.h" namespace blink { // The amount of time to wait before sending a fake mouse event, triggered // during a scroll. The short interval is used if the content responds to the mouse events quickly enough, // otherwise the long interval is used. static const double fakeMouseMoveShortInterval = 0.1; static const double fakeMouseMoveLongInterval = 0.250; // The amount of time to wait for a cursor update on style and layout changes // Set to 50Hz, no need to be faster than common screen refresh rate static const double cursorUpdateInterval = 0.02; static const int maximumCursorSize = 128; // It's pretty unlikely that a scale of less than one would ever be used. But all we really // need to ensure here is that the scale isn't so small that integer overflow can occur when // dividing cursor sizes (limited above) by the scale. static const double minimumCursorScale = 0.001; // The minimum amount of time an element stays active after a ShowPress // This is roughly 9 frames, which should be long enough to be noticeable. static const double minimumActiveInterval = 0.15; enum NoCursorChangeType { NoCursorChange }; class OptionalCursor { public: OptionalCursor(NoCursorChangeType) : m_isCursorChange(false) { } OptionalCursor(const Cursor& cursor) : m_isCursorChange(true), m_cursor(cursor) { } bool isCursorChange() const { return m_isCursorChange; } const Cursor& cursor() const { ASSERT(m_isCursorChange); return m_cursor; } private: bool m_isCursorChange; Cursor m_cursor; }; class MaximumDurationTracker { public: explicit MaximumDurationTracker(double *maxDuration) : m_maxDuration(maxDuration) , m_start(monotonicallyIncreasingTime()) { } ~MaximumDurationTracker() { *m_maxDuration = max(*m_maxDuration, monotonicallyIncreasingTime() - m_start); } private: double* m_maxDuration; double m_start; }; static inline ScrollGranularity wheelGranularityToScrollGranularity(unsigned deltaMode) { switch (deltaMode) { case WheelEvent::DOM_DELTA_PAGE: return ScrollByPage; case WheelEvent::DOM_DELTA_LINE: return ScrollByLine; case WheelEvent::DOM_DELTA_PIXEL: return ScrollByPixel; default: return ScrollByPixel; } } EventHandler::EventHandler(LocalFrame* frame) : m_frame(frame) , m_mousePressed(false) , m_capturesDragging(false) , m_mouseDownMayStartSelect(false) , m_mouseDownMayStartDrag(false) , m_mouseDownWasSingleClickInSelection(false) , m_selectionInitiationState(HaveNotStartedSelection) , m_hoverTimer(this, &EventHandler::hoverTimerFired) , m_cursorUpdateTimer(this, &EventHandler::cursorUpdateTimerFired) , m_mouseDownMayStartAutoscroll(false) , m_fakeMouseMoveEventTimer(this, &EventHandler::fakeMouseMoveEventTimerFired) , m_resizeScrollableArea(0) , m_eventHandlerWillResetCapturingMouseEventsNode(0) , m_clickCount(0) , m_shouldOnlyFireDragOverEvent(false) , m_mousePositionIsUnknown(true) , m_mouseDownTimestamp(0) , m_widgetIsLatched(false) , m_touchPressed(false) , m_scrollGestureHandlingNode(nullptr) , m_lastGestureScrollOverWidget(false) , m_maxMouseMovedDuration(0) , m_didStartDrag(false) , m_activeIntervalTimer(this, &EventHandler::activeIntervalTimerFired) , m_lastShowPressTimestamp(0) { } EventHandler::~EventHandler() { ASSERT(!m_fakeMouseMoveEventTimer.isActive()); } void EventHandler::trace(Visitor* visitor) { #if ENABLE(OILPAN) visitor->trace(m_mousePressNode); visitor->trace(m_capturingMouseEventsNode); visitor->trace(m_nodeUnderMouse); visitor->trace(m_lastNodeUnderMouse); visitor->trace(m_clickNode); visitor->trace(m_dragTarget); visitor->trace(m_latchedWheelEventNode); visitor->trace(m_previousWheelScrolledNode); visitor->trace(m_targetForTouchID); visitor->trace(m_touchSequenceDocument); visitor->trace(m_scrollGestureHandlingNode); visitor->trace(m_previousGestureScrolledNode); visitor->trace(m_lastDeferredTapElement); #endif } void EventHandler::clear() { m_hoverTimer.stop(); m_cursorUpdateTimer.stop(); m_fakeMouseMoveEventTimer.stop(); m_activeIntervalTimer.stop(); m_resizeScrollableArea = 0; m_nodeUnderMouse = nullptr; m_lastNodeUnderMouse = nullptr; m_lastScrollbarUnderMouse = nullptr; m_clickCount = 0; m_clickNode = nullptr; m_dragTarget = nullptr; m_shouldOnlyFireDragOverEvent = false; m_mousePositionIsUnknown = true; m_lastKnownMousePosition = IntPoint(); m_lastKnownMouseGlobalPosition = IntPoint(); m_lastMouseDownUserGestureToken.clear(); m_mousePressNode = nullptr; m_mousePressed = false; m_capturesDragging = false; m_capturingMouseEventsNode = nullptr; m_latchedWheelEventNode = nullptr; m_previousWheelScrolledNode = nullptr; m_targetForTouchID.clear(); m_touchSequenceDocument.clear(); m_touchSequenceUserGestureToken.clear(); m_scrollGestureHandlingNode = nullptr; m_lastGestureScrollOverWidget = false; m_previousGestureScrolledNode = nullptr; m_scrollbarHandlingScrollGesture = nullptr; m_maxMouseMovedDuration = 0; m_didStartDrag = false; m_touchPressed = false; m_mouseDownMayStartSelect = false; m_mouseDownMayStartDrag = false; m_lastShowPressTimestamp = 0; m_lastDeferredTapElement = nullptr; } void EventHandler::nodeWillBeRemoved(Node& nodeToBeRemoved) { if (!nodeToBeRemoved.containsIncludingShadowDOM(m_clickNode.get())) return; if (nodeToBeRemoved.isInShadowTree()) { m_clickNode = nodeToBeRemoved.parentOrShadowHostNode(); } else { // We don't dispatch click events if the mousedown node is removed // before a mouseup event. It is compatible with IE and Firefox. m_clickNode = nullptr; } } static void setSelectionIfNeeded(FrameSelection& selection, const VisibleSelection& newSelection) { if (selection.selection() != newSelection) selection.setSelection(newSelection); } static inline bool dispatchSelectStart(Node* node) { if (!node || !node->renderer()) return true; return node->dispatchEvent(Event::createCancelableBubble(EventTypeNames::selectstart)); } static VisibleSelection expandSelectionToRespectUserSelectAll(Node* targetNode, const VisibleSelection& selection) { Node* rootUserSelectAll = Position::rootUserSelectAllForNode(targetNode); if (!rootUserSelectAll) return selection; VisibleSelection newSelection(selection); newSelection.setBase(positionBeforeNode(rootUserSelectAll).upstream(CanCrossEditingBoundary)); newSelection.setExtent(positionAfterNode(rootUserSelectAll).downstream(CanCrossEditingBoundary)); return newSelection; } bool EventHandler::updateSelectionForMouseDownDispatchingSelectStart(Node* targetNode, const VisibleSelection& selection, TextGranularity granularity) { if (Position::nodeIsUserSelectNone(targetNode)) return false; if (!dispatchSelectStart(targetNode)) return false; if (selection.isRange()) m_selectionInitiationState = ExtendedSelection; else { granularity = CharacterGranularity; m_selectionInitiationState = PlacedCaret; } m_frame->selection().setNonDirectionalSelectionIfNeeded(selection, granularity); return true; } void EventHandler::selectClosestWordFromHitTestResult(const HitTestResult& result, AppendTrailingWhitespace appendTrailingWhitespace) { Node* innerNode = result.targetNode(); VisibleSelection newSelection; if (innerNode && innerNode->renderer()) { VisiblePosition pos(innerNode->renderer()->positionForPoint(result.localPoint())); if (pos.isNotNull()) { newSelection = VisibleSelection(pos); newSelection.expandUsingGranularity(WordGranularity); } if (appendTrailingWhitespace == ShouldAppendTrailingWhitespace && newSelection.isRange()) newSelection.appendTrailingWhitespace(); updateSelectionForMouseDownDispatchingSelectStart(innerNode, expandSelectionToRespectUserSelectAll(innerNode, newSelection), WordGranularity); } } void EventHandler::selectClosestMisspellingFromHitTestResult(const HitTestResult& result, AppendTrailingWhitespace appendTrailingWhitespace) { Node* innerNode = result.targetNode(); VisibleSelection newSelection; if (innerNode && innerNode->renderer()) { VisiblePosition pos(innerNode->renderer()->positionForPoint(result.localPoint())); Position start = pos.deepEquivalent(); Position end = pos.deepEquivalent(); if (pos.isNotNull()) { DocumentMarkerVector markers = innerNode->document().markers().markersInRange(makeRange(pos, pos).get(), DocumentMarker::MisspellingMarkers()); if (markers.size() == 1) { start.moveToOffset(markers[0]->startOffset()); end.moveToOffset(markers[0]->endOffset()); newSelection = VisibleSelection(start, end); } } if (appendTrailingWhitespace == ShouldAppendTrailingWhitespace && newSelection.isRange()) newSelection.appendTrailingWhitespace(); updateSelectionForMouseDownDispatchingSelectStart(innerNode, expandSelectionToRespectUserSelectAll(innerNode, newSelection), WordGranularity); } } void EventHandler::selectClosestWordFromMouseEvent(const MouseEventWithHitTestResults& result) { if (m_mouseDownMayStartSelect) { selectClosestWordFromHitTestResult(result.hitTestResult(), DontAppendTrailingWhitespace); } } void EventHandler::selectClosestMisspellingFromMouseEvent(const MouseEventWithHitTestResults& result) { if (m_mouseDownMayStartSelect) { selectClosestMisspellingFromHitTestResult(result.hitTestResult(), DontAppendTrailingWhitespace); } } void EventHandler::selectClosestWordOrLinkFromMouseEvent(const MouseEventWithHitTestResults& result) { if (!result.hitTestResult().isLiveLink()) return selectClosestWordFromMouseEvent(result); Node* innerNode = result.targetNode(); if (innerNode && innerNode->renderer() && m_mouseDownMayStartSelect) { VisibleSelection newSelection; Element* URLElement = result.hitTestResult().URLElement(); VisiblePosition pos(innerNode->renderer()->positionForPoint(result.localPoint())); if (pos.isNotNull() && pos.deepEquivalent().deprecatedNode()->isDescendantOf(URLElement)) newSelection = VisibleSelection::selectionFromContentsOfNode(URLElement); updateSelectionForMouseDownDispatchingSelectStart(innerNode, expandSelectionToRespectUserSelectAll(innerNode, newSelection), WordGranularity); } } bool EventHandler::handleMousePressEventDoubleClick(const MouseEventWithHitTestResults& event) { TRACE_EVENT0("blink", "EventHandler::handleMousePressEventDoubleClick"); if (event.event().button() != LeftButton) return false; if (m_frame->selection().isRange()) { // A double-click when range is already selected // should not change the selection. So, do not call // selectClosestWordFromMouseEvent, but do set // m_beganSelectingText to prevent handleMouseReleaseEvent // from setting caret selection. m_selectionInitiationState = ExtendedSelection; } else { selectClosestWordFromMouseEvent(event); } return true; } bool EventHandler::handleMousePressEventTripleClick(const MouseEventWithHitTestResults& event) { TRACE_EVENT0("blink", "EventHandler::handleMousePressEventTripleClick"); if (event.event().button() != LeftButton) return false; Node* innerNode = event.targetNode(); if (!(innerNode && innerNode->renderer() && m_mouseDownMayStartSelect)) return false; VisibleSelection newSelection; VisiblePosition pos(innerNode->renderer()->positionForPoint(event.localPoint())); if (pos.isNotNull()) { newSelection = VisibleSelection(pos); newSelection.expandUsingGranularity(ParagraphGranularity); } return updateSelectionForMouseDownDispatchingSelectStart(innerNode, expandSelectionToRespectUserSelectAll(innerNode, newSelection), ParagraphGranularity); } static int textDistance(const Position& start, const Position& end) { RefPtr range = Range::create(*start.document(), start, end); return TextIterator::rangeLength(range.get(), true); } bool EventHandler::handleMousePressEventSingleClick(const MouseEventWithHitTestResults& event) { TRACE_EVENT0("blink", "EventHandler::handleMousePressEventSingleClick"); m_frame->document()->updateLayoutIgnorePendingStylesheets(); Node* innerNode = event.targetNode(); if (!(innerNode && innerNode->renderer() && m_mouseDownMayStartSelect)) return false; // Extend the selection if the Shift key is down, unless the click is in a link. bool extendSelection = event.event().shiftKey() && !event.isOverLink(); // Don't restart the selection when the mouse is pressed on an // existing selection so we can allow for text dragging. LayoutPoint vPoint = event.event().position(); if (!extendSelection && m_frame->selection().contains(vPoint)) { m_mouseDownWasSingleClickInSelection = true; return false; } VisiblePosition visiblePos(innerNode->renderer()->positionForPoint(event.localPoint())); if (visiblePos.isNull()) visiblePos = VisiblePosition(firstPositionInOrBeforeNode(innerNode), DOWNSTREAM); Position pos = visiblePos.deepEquivalent(); VisibleSelection newSelection = m_frame->selection().selection(); TextGranularity granularity = CharacterGranularity; if (extendSelection && newSelection.isCaretOrRange()) { VisibleSelection selectionInUserSelectAll(expandSelectionToRespectUserSelectAll(innerNode, VisibleSelection(VisiblePosition(pos)))); if (selectionInUserSelectAll.isRange()) { if (comparePositions(selectionInUserSelectAll.start(), newSelection.start()) < 0) pos = selectionInUserSelectAll.start(); else if (comparePositions(newSelection.end(), selectionInUserSelectAll.end()) < 0) pos = selectionInUserSelectAll.end(); } if (!m_frame->editor().behavior().shouldConsiderSelectionAsDirectional()) { if (pos.isNotNull()) { // See REGRESSION (Mail): shift-click deselects when selection // was created right-to-left Position start = newSelection.start(); Position end = newSelection.end(); int distanceToStart = textDistance(start, pos); int distanceToEnd = textDistance(pos, end); if (distanceToStart <= distanceToEnd) newSelection = VisibleSelection(end, pos); else newSelection = VisibleSelection(start, pos); } } else newSelection.setExtent(pos); if (m_frame->selection().granularity() != CharacterGranularity) { granularity = m_frame->selection().granularity(); newSelection.expandUsingGranularity(m_frame->selection().granularity()); } } else { newSelection = expandSelectionToRespectUserSelectAll(innerNode, VisibleSelection(visiblePos)); } // Updating the selection is considered side-effect of the event and so it doesn't impact the handled state. updateSelectionForMouseDownDispatchingSelectStart(innerNode, newSelection, granularity); return false; } static inline bool canMouseDownStartSelect(Node* node) { if (!node || !node->renderer()) return true; if (!node->canStartSelection()) return false; return true; } bool EventHandler::handleMousePressEvent(const MouseEventWithHitTestResults& event) { TRACE_EVENT0("blink", "EventHandler::handleMousePressEvent"); cancelFakeMouseMoveEvent(); m_frame->document()->updateLayoutIgnorePendingStylesheets(); bool singleClick = event.event().clickCount() <= 1; // If we got the event back, that must mean it wasn't prevented, // so it's allowed to start a drag or selection if it wasn't in a scrollbar. m_mouseDownMayStartSelect = canMouseDownStartSelect(event.targetNode()) && !event.scrollbar(); m_mouseDownMayStartDrag = singleClick; m_mouseDownWasSingleClickInSelection = false; m_mouseDown = event.event(); if (event.isOverWidget() && passWidgetMouseDownEventToWidget(event)) return true; // We don't do this at the start of mouse down handling, // because we don't want to do it until we know we didn't hit a widget. if (singleClick) focusDocumentView(); Node* innerNode = event.targetNode(); m_mousePressNode = innerNode; m_dragStartPos = event.event().position(); bool swallowEvent = false; m_mousePressed = true; m_selectionInitiationState = HaveNotStartedSelection; if (event.event().clickCount() == 2) swallowEvent = handleMousePressEventDoubleClick(event); else if (event.event().clickCount() >= 3) swallowEvent = handleMousePressEventTripleClick(event); else swallowEvent = handleMousePressEventSingleClick(event); m_mouseDownMayStartAutoscroll = m_mouseDownMayStartSelect || (m_mousePressNode && m_mousePressNode->renderBox() && m_mousePressNode->renderBox()->canBeProgramaticallyScrolled()); return swallowEvent; } bool EventHandler::handleMouseDraggedEvent(const MouseEventWithHitTestResults& event) { TRACE_EVENT0("blink", "EventHandler::handleMouseDraggedEvent"); if (!m_mousePressed) return false; Node* targetNode = event.targetNode(); if (event.event().button() != LeftButton || !targetNode) return false; RenderObject* renderer = targetNode->renderer(); if (!renderer) { Node* parent = NodeRenderingTraversal::parent(targetNode); if (!parent) return false; renderer = parent->renderer(); if (!renderer) return false; } m_mouseDownMayStartDrag = false; if (m_mouseDownMayStartAutoscroll) { if (AutoscrollController* controller = autoscrollController()) { controller->startAutoscrollForSelection(renderer); m_mouseDownMayStartAutoscroll = false; } } if (m_selectionInitiationState != ExtendedSelection) { HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active); HitTestResult result(m_mouseDownPos); m_frame->document()->renderView()->hitTest(request, result); updateSelectionForMouseDrag(result); } updateSelectionForMouseDrag(event.hitTestResult()); return true; } void EventHandler::updateSelectionForMouseDrag() { FrameView* view = m_frame->view(); if (!view) return; RenderView* renderer = m_frame->contentRenderer(); if (!renderer) return; HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::Move); HitTestResult result(m_lastKnownMousePosition); renderer->hitTest(request, result); updateSelectionForMouseDrag(result); } void EventHandler::updateSelectionForMouseDrag(const HitTestResult& hitTestResult) { if (!m_mouseDownMayStartSelect) return; Node* target = hitTestResult.targetNode(); if (!target) return; VisiblePosition targetPosition = m_frame->selection().selection().visiblePositionRespectingEditingBoundary(hitTestResult.localPoint(), target); // Don't modify the selection if we're not on a node. if (targetPosition.isNull()) return; // Restart the selection if this is the first mouse move. This work is usually // done in handleMousePressEvent, but not if the mouse press was on an existing selection. VisibleSelection newSelection = m_frame->selection().selection(); if (m_selectionInitiationState == HaveNotStartedSelection && !dispatchSelectStart(target)) return; if (m_selectionInitiationState != ExtendedSelection) { // Always extend selection here because it's caused by a mouse drag m_selectionInitiationState = ExtendedSelection; newSelection = VisibleSelection(targetPosition); } if (RuntimeEnabledFeatures::userSelectAllEnabled()) { Node* rootUserSelectAllForMousePressNode = Position::rootUserSelectAllForNode(m_mousePressNode.get()); if (rootUserSelectAllForMousePressNode && rootUserSelectAllForMousePressNode == Position::rootUserSelectAllForNode(target)) { newSelection.setBase(positionBeforeNode(rootUserSelectAllForMousePressNode).upstream(CanCrossEditingBoundary)); newSelection.setExtent(positionAfterNode(rootUserSelectAllForMousePressNode).downstream(CanCrossEditingBoundary)); } else { // Reset base for user select all when base is inside user-select-all area and extent < base. if (rootUserSelectAllForMousePressNode && comparePositions(target->renderer()->positionForPoint(hitTestResult.localPoint()), m_mousePressNode->renderer()->positionForPoint(m_dragStartPos)) < 0) newSelection.setBase(positionAfterNode(rootUserSelectAllForMousePressNode).downstream(CanCrossEditingBoundary)); Node* rootUserSelectAllForTarget = Position::rootUserSelectAllForNode(target); if (rootUserSelectAllForTarget && m_mousePressNode->renderer() && comparePositions(target->renderer()->positionForPoint(hitTestResult.localPoint()), m_mousePressNode->renderer()->positionForPoint(m_dragStartPos)) < 0) newSelection.setExtent(positionBeforeNode(rootUserSelectAllForTarget).upstream(CanCrossEditingBoundary)); else if (rootUserSelectAllForTarget && m_mousePressNode->renderer()) newSelection.setExtent(positionAfterNode(rootUserSelectAllForTarget).downstream(CanCrossEditingBoundary)); else newSelection.setExtent(targetPosition); } } else { newSelection.setExtent(targetPosition); } if (m_frame->selection().granularity() != CharacterGranularity) newSelection.expandUsingGranularity(m_frame->selection().granularity()); m_frame->selection().setNonDirectionalSelectionIfNeeded(newSelection, m_frame->selection().granularity(), FrameSelection::AdjustEndpointsAtBidiBoundary); } bool EventHandler::handleMouseReleaseEvent(const MouseEventWithHitTestResults& event) { AutoscrollController* controller = autoscrollController(); if (controller && controller->autoscrollInProgress()) stopAutoscroll(); // Used to prevent mouseMoveEvent from initiating a drag before // the mouse is pressed again. m_mousePressed = false; m_capturesDragging = false; m_mouseDownMayStartDrag = false; m_mouseDownMayStartSelect = false; m_mouseDownMayStartAutoscroll = false; bool handled = false; // Clear the selection if the mouse didn't move after the last mouse // press and it's not a context menu click. We do this so when clicking // on the selection, the selection goes away. However, if we are // editing, place the caret. if (m_mouseDownWasSingleClickInSelection && m_selectionInitiationState != ExtendedSelection && m_dragStartPos == event.event().position() && m_frame->selection().isRange() && event.event().button() != RightButton) { VisibleSelection newSelection; Node* node = event.targetNode(); if (node && node->renderer() && node->hasEditableStyle()) { VisiblePosition pos = VisiblePosition(node->renderer()->positionForPoint(event.localPoint())); newSelection = VisibleSelection(pos); } setSelectionIfNeeded(m_frame->selection(), newSelection); handled = true; } m_frame->selection().notifyRendererOfSelectionChange(UserTriggered); m_frame->selection().selectFrameElementInParentIfFullySelected(); if (event.event().button() == MiddleButton && !event.isOverLink()) { // Ignore handled, since we want to paste to where the caret was placed anyway. handled = handlePasteGlobalSelection(event.event()) || handled; } return handled; } AutoscrollController* EventHandler::autoscrollController() const { if (Page* page = m_frame->page()) return &page->autoscrollController(); return 0; } HitTestResult EventHandler::hitTestResultAtPoint(const LayoutPoint& point, HitTestRequest::HitTestRequestType hitType, const LayoutSize& padding) { TRACE_EVENT0("blink", "EventHandler::hitTestResultAtPoint"); HitTestResult result(point, padding.height(), padding.width(), padding.height(), padding.width()); // RenderView::hitTest causes a layout, and we don't want to hit that until the first // layout because until then, there is nothing shown on the screen - the user can't // have intentionally clicked on something belonging to this page. Furthermore, // mousemove events before the first layout should not lead to a premature layout() // happening, which could show a flash of white. // See also the similar code in Document::prepareMouseEvent. if (!m_frame->contentRenderer() || !m_frame->view() || !m_frame->view()->didFirstLayout()) return result; // hitTestResultAtPoint is specifically used to hitTest into all frames, thus it always allows child frame content. HitTestRequest request(hitType | HitTestRequest::AllowChildFrameContent); m_frame->contentRenderer()->hitTest(request, result); if (!request.readOnly()) m_frame->document()->updateHoverActiveState(request, result.innerElement()); return result; } void EventHandler::stopAutoscroll() { if (AutoscrollController* controller = autoscrollController()) controller->stopAutoscroll(); } Node* EventHandler::mousePressNode() const { return m_mousePressNode.get(); } bool EventHandler::scroll(ScrollDirection direction, ScrollGranularity granularity, Node* startNode, Node** stopNode, float delta, IntPoint absolutePoint) { if (!delta) return false; Node* node = startNode; if (!node) node = m_frame->document()->focusedElement(); if (!node) node = m_mousePressNode.get(); if (!node || !node->renderer()) return false; RenderBox* curBox = node->renderer()->enclosingBox(); while (curBox && !curBox->isRenderView()) { // If we're at the stopNode, we should try to scroll it but we shouldn't bubble past it bool shouldStopBubbling = stopNode && *stopNode && curBox->node() == *stopNode; bool didScroll = curBox->scroll(direction, granularity, delta); if (didScroll && stopNode) *stopNode = curBox->node(); if (didScroll || shouldStopBubbling) { return true; } curBox = curBox->containingBlock(); } return false; } bool EventHandler::bubblingScroll(ScrollDirection direction, ScrollGranularity granularity, Node* startingNode) { // The layout needs to be up to date to determine if we can scroll. We may be // here because of an onLoad event, in which case the final layout hasn't been performed yet. m_frame->document()->updateLayoutIgnorePendingStylesheets(); if (scroll(direction, granularity, startingNode)) return true; return false; } IntPoint EventHandler::lastKnownMousePosition() const { return m_lastKnownMousePosition; } bool EventHandler::useHandCursor(Node* node, bool isOverLink) { if (!node) return false; return isOverLink && !node->hasEditableStyle(); } void EventHandler::cursorUpdateTimerFired(Timer*) { ASSERT(m_frame); ASSERT(m_frame->document()); updateCursor(); } void EventHandler::updateCursor() { if (m_mousePositionIsUnknown) return; FrameView* view = m_frame->view(); if (!view || !view->shouldSetCursor()) return; RenderView* renderView = view->renderView(); if (!renderView) return; m_frame->document()->updateLayout(); HitTestRequest request(HitTestRequest::ReadOnly); HitTestResult result(m_lastKnownMousePosition); renderView->hitTest(request, result); OptionalCursor optionalCursor = selectCursor(result); if (optionalCursor.isCursorChange()) { m_currentMouseCursor = optionalCursor.cursor(); view->setCursor(m_currentMouseCursor); } } OptionalCursor EventHandler::selectCursor(const HitTestResult& result) { if (m_resizeScrollableArea && m_resizeScrollableArea->inResizeMode()) return NoCursorChange; Page* page = m_frame->page(); if (!page) return NoCursorChange; Node* node = result.innerPossiblyPseudoNode(); if (!node) return selectAutoCursor(result, node, iBeamCursor()); RenderObject* renderer = node->renderer(); RenderStyle* style = renderer ? renderer->style() : 0; if (renderer) { Cursor overrideCursor; switch (renderer->getCursor(roundedIntPoint(result.localPoint()), overrideCursor)) { case SetCursorBasedOnStyle: break; case SetCursor: return overrideCursor; case DoNotSetCursor: return NoCursorChange; } } if (style && style->cursors()) { const CursorList* cursors = style->cursors(); for (unsigned i = 0; i < cursors->size(); ++i) { StyleImage* styleImage = (*cursors)[i].image(); if (!styleImage) continue; ImageResource* cachedImage = styleImage->cachedImage(); if (!cachedImage) continue; float scale = styleImage->imageScaleFactor(); // Get hotspot and convert from logical pixels to physical pixels. IntPoint hotSpot = (*cursors)[i].hotSpot(); hotSpot.scale(scale, scale); IntSize size = cachedImage->imageForRenderer(renderer)->size(); if (cachedImage->errorOccurred()) continue; // Limit the size of cursors (in UI pixels) so that they cannot be // used to cover UI elements in chrome. size.scale(1 / scale); if (size.width() > maximumCursorSize || size.height() > maximumCursorSize) continue; Image* image = cachedImage->imageForRenderer(renderer); // Ensure no overflow possible in calculations above. if (scale < minimumCursorScale) continue; return Cursor(image, hotSpot, scale); } } switch (style ? style->cursor() : CURSOR_AUTO) { case CURSOR_AUTO: { const Cursor& iBeam = iBeamCursor(); return selectAutoCursor(result, node, iBeam); } case CURSOR_CROSS: return crossCursor(); case CURSOR_POINTER: return handCursor(); case CURSOR_MOVE: return moveCursor(); case CURSOR_ALL_SCROLL: return moveCursor(); case CURSOR_E_RESIZE: return eastResizeCursor(); case CURSOR_W_RESIZE: return westResizeCursor(); case CURSOR_N_RESIZE: return northResizeCursor(); case CURSOR_S_RESIZE: return southResizeCursor(); case CURSOR_NE_RESIZE: return northEastResizeCursor(); case CURSOR_SW_RESIZE: return southWestResizeCursor(); case CURSOR_NW_RESIZE: return northWestResizeCursor(); case CURSOR_SE_RESIZE: return southEastResizeCursor(); case CURSOR_NS_RESIZE: return northSouthResizeCursor(); case CURSOR_EW_RESIZE: return eastWestResizeCursor(); case CURSOR_NESW_RESIZE: return northEastSouthWestResizeCursor(); case CURSOR_NWSE_RESIZE: return northWestSouthEastResizeCursor(); case CURSOR_COL_RESIZE: return columnResizeCursor(); case CURSOR_ROW_RESIZE: return rowResizeCursor(); case CURSOR_TEXT: return iBeamCursor(); case CURSOR_WAIT: return waitCursor(); case CURSOR_HELP: return helpCursor(); case CURSOR_VERTICAL_TEXT: return verticalTextCursor(); case CURSOR_CELL: return cellCursor(); case CURSOR_CONTEXT_MENU: return contextMenuCursor(); case CURSOR_PROGRESS: return progressCursor(); case CURSOR_NO_DROP: return noDropCursor(); case CURSOR_ALIAS: return aliasCursor(); case CURSOR_COPY: return copyCursor(); case CURSOR_NONE: return noneCursor(); case CURSOR_NOT_ALLOWED: return notAllowedCursor(); case CURSOR_DEFAULT: return pointerCursor(); case CURSOR_ZOOM_IN: return zoomInCursor(); case CURSOR_ZOOM_OUT: return zoomOutCursor(); case CURSOR_WEBKIT_GRAB: return grabCursor(); case CURSOR_WEBKIT_GRABBING: return grabbingCursor(); } return pointerCursor(); } OptionalCursor EventHandler::selectAutoCursor(const HitTestResult& result, Node* node, const Cursor& iBeam) { bool editable = (node && node->hasEditableStyle()); if (useHandCursor(node, result.isOverLink())) return handCursor(); bool inResizer = false; RenderObject* renderer = node ? node->renderer() : 0; if (renderer && m_frame->view()) { RenderLayer* layer = renderer->enclosingLayer(); inResizer = layer->scrollableArea() && layer->scrollableArea()->isPointInResizeControl(result.roundedPointInMainFrame(), ResizerForPointer); } // During selection, use an I-beam no matter what we're over. // If a drag may be starting or we're capturing mouse events for a particular node, don't treat this as a selection. if (m_mousePressed && m_mouseDownMayStartSelect && !m_mouseDownMayStartDrag && m_frame->selection().isCaretOrRange() && !m_capturingMouseEventsNode) { return iBeam; } if ((editable || (renderer && renderer->isText() && node->canStartSelection())) && !inResizer && !result.scrollbar()) return iBeam; return pointerCursor(); } static LayoutPoint documentPointForWindowPoint(LocalFrame* frame, const IntPoint& windowPoint) { // FIXME(sky): remove return windowPoint; } bool EventHandler::handleMousePressEvent(const PlatformMouseEvent& mouseEvent) { TRACE_EVENT0("blink", "EventHandler::handleMousePressEvent"); RefPtr protector(m_frame->view()); UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture); m_frame->eventHandler().m_lastMouseDownUserGestureToken = gestureIndicator.currentToken(); cancelFakeMouseMoveEvent(); if (m_eventHandlerWillResetCapturingMouseEventsNode) m_capturingMouseEventsNode = nullptr; m_mousePressed = true; m_capturesDragging = true; setLastKnownMousePosition(mouseEvent); m_mouseDownTimestamp = mouseEvent.timestamp(); m_mouseDownMayStartDrag = false; m_mouseDownMayStartSelect = false; m_mouseDownMayStartAutoscroll = false; if (m_frame->view()) m_mouseDownPos = mouseEvent.position(); else { // FIXME(sky): Why do we have this else block at all? invalidateClick(); return false; } // Mouse events simulated from touch should not hit-test again. ASSERT(!mouseEvent.fromTouch()); HitTestRequest request(HitTestRequest::Active); // Save the document point we generate in case the window coordinate is invalidated by what happens // when we dispatch the event. LayoutPoint documentPoint = documentPointForWindowPoint(m_frame, mouseEvent.position()); MouseEventWithHitTestResults mev = m_frame->document()->prepareMouseEvent(request, documentPoint, mouseEvent); if (!mev.targetNode()) { invalidateClick(); return false; } m_mousePressNode = mev.targetNode(); m_clickCount = mouseEvent.clickCount(); m_clickNode = mev.targetNode()->isTextNode() ? NodeRenderingTraversal::parent(mev.targetNode()) : mev.targetNode(); RenderLayer* layer = mev.targetNode()->renderer() ? mev.targetNode()->renderer()->enclosingLayer() : 0; IntPoint p = mouseEvent.position(); if (layer && layer->scrollableArea() && layer->scrollableArea()->isPointInResizeControl(p, ResizerForPointer)) { m_resizeScrollableArea = layer->scrollableArea(); m_resizeScrollableArea->setInResizeMode(true); m_offsetFromResizeCorner = m_resizeScrollableArea->offsetFromResizeCorner(p); invalidateClick(); return true; } m_frame->selection().setCaretBlinkingSuspended(true); bool swallowEvent = !dispatchMouseEvent(EventTypeNames::mousedown, mev.targetNode(), m_clickCount, mouseEvent, true); swallowEvent = swallowEvent || handleMouseFocus(mouseEvent); m_capturesDragging = !swallowEvent || mev.scrollbar(); // If the hit testing originally determined the event was in a scrollbar, refetch the MouseEventWithHitTestResults // in case the scrollbar widget was destroyed when the mouse event was handled. if (mev.scrollbar()) { const bool wasLastScrollBar = mev.scrollbar() == m_lastScrollbarUnderMouse.get(); HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active); mev = m_frame->document()->prepareMouseEvent(request, documentPoint, mouseEvent); if (wasLastScrollBar && mev.scrollbar() != m_lastScrollbarUnderMouse.get()) m_lastScrollbarUnderMouse = nullptr; } if (swallowEvent) { // scrollbars should get events anyway, even disabled controls might be scrollable passMousePressEventToScrollbar(mev); } else { if (passMousePressEventToScrollbar(mev)) swallowEvent = true; else swallowEvent = handleMousePressEvent(mev); } return swallowEvent; } static RenderLayer* layerForNode(Node* node) { if (!node) return 0; RenderObject* renderer = node->renderer(); if (!renderer) return 0; RenderLayer* layer = renderer->enclosingLayer(); if (!layer) return 0; return layer; } ScrollableArea* EventHandler::associatedScrollableArea(const RenderLayer* layer) const { if (RenderLayerScrollableArea* scrollableArea = layer->scrollableArea()) { if (scrollableArea->scrollsOverflow()) return scrollableArea; } return 0; } bool EventHandler::handleMouseMoveEvent(const PlatformMouseEvent& event) { TRACE_EVENT0("blink", "EventHandler::handleMouseMoveEvent"); RefPtr protector(m_frame->view()); MaximumDurationTracker maxDurationTracker(&m_maxMouseMovedDuration); HitTestResult hoveredNode = HitTestResult(LayoutPoint()); bool result = handleMouseMoveOrLeaveEvent(event, &hoveredNode); Page* page = m_frame->page(); if (!page) return result; if (RenderLayer* layer = layerForNode(hoveredNode.innerNode())) { if (ScrollableArea* layerScrollableArea = associatedScrollableArea(layer)) layerScrollableArea->mouseMovedInContentArea(); } page->chrome().mouseDidMoveOverElement(hoveredNode, event.modifierFlags()); page->chrome().setToolTip(hoveredNode); return result; } void EventHandler::handleMouseLeaveEvent(const PlatformMouseEvent& event) { TRACE_EVENT0("blink", "EventHandler::handleMouseLeaveEvent"); RefPtr protector(m_frame->view()); handleMouseMoveOrLeaveEvent(event); } bool EventHandler::handleMouseMoveOrLeaveEvent(const PlatformMouseEvent& mouseEvent, HitTestResult* hoveredNode, bool onlyUpdateScrollbars) { ASSERT(m_frame); ASSERT(m_frame->view()); setLastKnownMousePosition(mouseEvent); if (m_hoverTimer.isActive()) m_hoverTimer.stop(); m_cursorUpdateTimer.stop(); cancelFakeMouseMoveEvent(); // Send events right to a scrollbar if the mouse is pressed. if (m_lastScrollbarUnderMouse && m_mousePressed) { m_lastScrollbarUnderMouse->mouseMoved(mouseEvent); return true; } // Mouse events simulated from touch should not hit-test again. ASSERT(!mouseEvent.fromTouch()); HitTestRequest::HitTestRequestType hitType = HitTestRequest::Move; if (m_mousePressed) hitType |= HitTestRequest::Active; else if (onlyUpdateScrollbars) { // Mouse events should be treated as "read-only" if we're updating only scrollbars. This // means that :hover and :active freeze in the state they were in, rather than updating // for nodes the mouse moves while the window is not key (which will be the case if // onlyUpdateScrollbars is true). hitType |= HitTestRequest::ReadOnly; } // Treat any mouse move events as readonly if the user is currently touching the screen. if (m_touchPressed) hitType |= HitTestRequest::Active | HitTestRequest::ReadOnly; HitTestRequest request(hitType); MouseEventWithHitTestResults mev = prepareMouseEvent(request, mouseEvent); if (hoveredNode) *hoveredNode = mev.hitTestResult(); Scrollbar* scrollbar = 0; if (m_resizeScrollableArea && m_resizeScrollableArea->inResizeMode()) m_resizeScrollableArea->resize(mouseEvent, m_offsetFromResizeCorner); else { scrollbar = mev.scrollbar(); updateLastScrollbarUnderMouse(scrollbar, !m_mousePressed); if (onlyUpdateScrollbars) return true; } bool swallowEvent = false; if (scrollbar && !m_mousePressed) scrollbar->mouseMoved(mouseEvent); // Handle hover effects on platforms that support visual feedback on scrollbar hovering. if (FrameView* view = m_frame->view()) { OptionalCursor optionalCursor = selectCursor(mev.hitTestResult()); if (optionalCursor.isCursorChange()) { m_currentMouseCursor = optionalCursor.cursor(); view->setCursor(m_currentMouseCursor); } } if (swallowEvent) return true; swallowEvent = !dispatchMouseEvent(EventTypeNames::mousemove, mev.targetNode(), 0, mouseEvent, true); if (!swallowEvent) swallowEvent = handleMouseDraggedEvent(mev); return swallowEvent; } void EventHandler::invalidateClick() { m_clickCount = 0; m_clickNode = nullptr; } bool EventHandler::handleMouseReleaseEvent(const PlatformMouseEvent& mouseEvent) { TRACE_EVENT0("blink", "EventHandler::handleMouseReleaseEvent"); RefPtr protector(m_frame->view()); m_frame->selection().setCaretBlinkingSuspended(false); OwnPtr gestureIndicator; if (m_frame->eventHandler().m_lastMouseDownUserGestureToken) gestureIndicator = adoptPtr(new UserGestureIndicator(m_frame->eventHandler().m_lastMouseDownUserGestureToken.release())); else gestureIndicator = adoptPtr(new UserGestureIndicator(DefinitelyProcessingUserGesture)); m_mousePressed = false; setLastKnownMousePosition(mouseEvent); if (m_lastScrollbarUnderMouse) { invalidateClick(); m_lastScrollbarUnderMouse->mouseUp(mouseEvent); bool setUnder = false; return !dispatchMouseEvent(EventTypeNames::mouseup, m_lastNodeUnderMouse.get(), m_clickCount, mouseEvent, setUnder); } // Mouse events simulated from touch should not hit-test again. ASSERT(!mouseEvent.fromTouch()); HitTestRequest::HitTestRequestType hitType = HitTestRequest::Release; HitTestRequest request(hitType); MouseEventWithHitTestResults mev = prepareMouseEvent(request, mouseEvent); if (m_eventHandlerWillResetCapturingMouseEventsNode) m_capturingMouseEventsNode = nullptr; bool swallowMouseUpEvent = !dispatchMouseEvent(EventTypeNames::mouseup, mev.targetNode(), m_clickCount, mouseEvent, false); bool swallowClickEvent = false; if (m_clickCount > 0 && mev.targetNode() && m_clickNode) { if (Node* clickTargetNode = NodeRenderingTraversal::commonAncestor(*mev.targetNode(), *m_clickNode)) swallowClickEvent = !dispatchMouseEvent(EventTypeNames::click, clickTargetNode, m_clickCount, mouseEvent, true); } if (m_resizeScrollableArea) { m_resizeScrollableArea->setInResizeMode(false); m_resizeScrollableArea = 0; } bool swallowMouseReleaseEvent = false; if (!swallowMouseUpEvent) swallowMouseReleaseEvent = handleMouseReleaseEvent(mev); invalidateClick(); return swallowMouseUpEvent || swallowClickEvent || swallowMouseReleaseEvent; } bool EventHandler::handlePasteGlobalSelection(const PlatformMouseEvent& mouseEvent) { // If the event was a middle click, attempt to copy global selection in after // the newly set caret position. // // This code is called from either the mouse up or mouse down handling. There // is some debate about when the global selection is pasted: // xterm: pastes on up. // GTK: pastes on down. // Qt: pastes on up. // Firefox: pastes on up. // Chromium: pastes on up. // // There is something of a webcompat angle to this well, as highlighted by // crbug.com/14608. Pages can clear text boxes 'onclick' and, if we paste on // down then the text is pasted just before the onclick handler runs and // clears the text box. So it's important this happens after the event // handlers have been fired. if (mouseEvent.type() != PlatformEvent::MouseReleased) return false; if (!m_frame->page()) return false; LocalFrame* focusFrame = m_frame->page()->focusController().focusedOrMainFrame(); // Do not paste here if the focus was moved somewhere else. if (m_frame == focusFrame && m_frame->editor().behavior().supportsGlobalSelection()) return m_frame->editor().command("PasteGlobalSelection").execute(); return false; } void EventHandler::setCapturingMouseEventsNode(PassRefPtr n) { m_capturingMouseEventsNode = n; m_eventHandlerWillResetCapturingMouseEventsNode = false; } MouseEventWithHitTestResults EventHandler::prepareMouseEvent(const HitTestRequest& request, const PlatformMouseEvent& mev) { ASSERT(m_frame); ASSERT(m_frame->document()); return m_frame->document()->prepareMouseEvent(request, documentPointForWindowPoint(m_frame, mev.position()), mev); } void EventHandler::updateMouseEventTargetNode(Node* targetNode, const PlatformMouseEvent& mouseEvent, bool fireMouseOverOut) { Node* result = targetNode; // If we're capturing, we always go right to that node. if (m_capturingMouseEventsNode) result = m_capturingMouseEventsNode.get(); else { // If the target node is a text node, dispatch on the parent node - rdar://4196646 if (result && result->isTextNode()) result = NodeRenderingTraversal::parent(result); } m_nodeUnderMouse = result; // Fire mouseout/mouseover if the mouse has shifted to a different node. if (fireMouseOverOut) { RenderLayer* layerForLastNode = layerForNode(m_lastNodeUnderMouse.get()); RenderLayer* layerForNodeUnderMouse = layerForNode(m_nodeUnderMouse.get()); Page* page = m_frame->page(); if (page && (layerForLastNode && (!layerForNodeUnderMouse || layerForNodeUnderMouse != layerForLastNode))) { // The mouse has moved between layers. if (ScrollableArea* scrollableAreaForLastNode = associatedScrollableArea(layerForLastNode)) scrollableAreaForLastNode->mouseExitedContentArea(); } if (page && (layerForNodeUnderMouse && (!layerForLastNode || layerForNodeUnderMouse != layerForLastNode))) { // The mouse has moved between layers. if (ScrollableArea* scrollableAreaForNodeUnderMouse = associatedScrollableArea(layerForNodeUnderMouse)) scrollableAreaForNodeUnderMouse->mouseEnteredContentArea(); } if (m_lastNodeUnderMouse && m_lastNodeUnderMouse->document() != m_frame->document()) { m_lastNodeUnderMouse = nullptr; m_lastScrollbarUnderMouse = nullptr; } if (m_lastNodeUnderMouse != m_nodeUnderMouse) { // send mouseout event to the old node if (m_lastNodeUnderMouse) m_lastNodeUnderMouse->dispatchMouseEvent(mouseEvent, EventTypeNames::mouseout, 0, m_nodeUnderMouse.get()); // send mouseover event to the new node if (m_nodeUnderMouse) m_nodeUnderMouse->dispatchMouseEvent(mouseEvent, EventTypeNames::mouseover, 0, m_lastNodeUnderMouse.get()); } m_lastNodeUnderMouse = m_nodeUnderMouse; } } // The return value means 'continue default handling.' bool EventHandler::dispatchMouseEvent(const AtomicString& eventType, Node* targetNode, int clickCount, const PlatformMouseEvent& mouseEvent, bool setUnder) { updateMouseEventTargetNode(targetNode, mouseEvent, setUnder); return !m_nodeUnderMouse || m_nodeUnderMouse->dispatchMouseEvent(mouseEvent, eventType, clickCount); } // The return value means 'swallow event' (was handled), as for other handle* functions. bool EventHandler::handleMouseFocus(const PlatformMouseEvent& mouseEvent) { // The layout needs to be up to date to determine if an element is focusable. m_frame->document()->updateLayoutIgnorePendingStylesheets(); Element* element = 0; if (m_nodeUnderMouse) element = m_nodeUnderMouse->isElementNode() ? toElement(m_nodeUnderMouse) : m_nodeUnderMouse->parentOrShadowHostElement(); for (; element; element = element->parentOrShadowHostElement()) { if (element->isFocusable() && element->focused()) return false; if (element->isMouseFocusable()) break; } ASSERT(!element || element->isMouseFocusable()); // To fix Can't drag selected ToDo, we don't focus // a node on mouse down if it's selected and inside a focused node. It will // be focused if the user does a mouseup over it, however, because the // mouseup will set a selection inside it, which will call // FrameSelection::setFocusedNodeIfNeeded. if (element && m_frame->selection().isRange() && m_frame->selection().toNormalizedRange()->compareNode(element, IGNORE_EXCEPTION) == Range::NODE_INSIDE && element->isDescendantOf(m_frame->document()->focusedElement())) return false; // Only change the focus when clicking scrollbars if it can transfered to a // mouse focusable node. if (!element && isInsideScrollbar(mouseEvent.position())) return true; if (Page* page = m_frame->page()) { // If focus shift is blocked, we eat the event. Note we should never // clear swallowEvent if the page already set it (e.g., by canceling // default behavior). if (element) { if (!page->focusController().setFocusedElement(element, m_frame, FocusTypeMouse)) return true; } else { // We call setFocusedElement even with !element in order to blur // current focus element when a link is clicked; this is expected by // some sites that rely on onChange handlers running from form // fields before the button click is processed. if (!page->focusController().setFocusedElement(0, m_frame)) return true; } } return false; } bool EventHandler::isInsideScrollbar(const IntPoint& windowPoint) const { if (RenderView* renderView = m_frame->contentRenderer()) { HitTestRequest request(HitTestRequest::ReadOnly); HitTestResult result(windowPoint); renderView->hitTest(request, result); return result.scrollbar(); } return false; } bool EventHandler::handleWheelEvent(const PlatformWheelEvent& event) { Document* doc = m_frame->document(); if (!doc->renderView()) return false; RefPtr protector(m_frame->view()); FrameView* view = m_frame->view(); if (!view) return false; LayoutPoint vPoint = event.position(); HitTestRequest request(HitTestRequest::ReadOnly); HitTestResult result(vPoint); doc->renderView()->hitTest(request, result); Node* node = result.innerNode(); // Wheel events should not dispatch to text nodes. if (node && node->isTextNode()) node = NodeRenderingTraversal::parent(node); bool isOverWidget; if (event.useLatchedEventNode()) { if (!m_latchedWheelEventNode) { m_latchedWheelEventNode = node; m_widgetIsLatched = result.isOverWidget(); } else node = m_latchedWheelEventNode.get(); isOverWidget = m_widgetIsLatched; } else { if (m_latchedWheelEventNode) m_latchedWheelEventNode = nullptr; if (m_previousWheelScrolledNode) m_previousWheelScrolledNode = nullptr; isOverWidget = result.isOverWidget(); } if (node) { if (node && !node->dispatchWheelEvent(event)) return true; } // We do another check on the frame view because the event handler can run JS which results in the frame getting destroyed. view = m_frame->view(); if (!view || !view->wheelEvent(event)) return false; return true; } void EventHandler::defaultWheelEventHandler(Node* startNode, WheelEvent* wheelEvent) { if (!startNode || !wheelEvent) return; // Ctrl + scrollwheel is reserved for triggering zoom in/out actions in Chromium. if (wheelEvent->ctrlKey()) return; Node* stopNode = m_previousWheelScrolledNode.get(); ScrollGranularity granularity = wheelGranularityToScrollGranularity(wheelEvent->deltaMode()); // Break up into two scrolls if we need to. Diagonal movement on // a MacBook pro is an example of a 2-dimensional mouse wheel event (where both deltaX and deltaY can be set). if (scroll(ScrollRight, granularity, startNode, &stopNode, wheelEvent->deltaX(), roundedIntPoint(wheelEvent->absoluteLocation()))) wheelEvent->setDefaultHandled(); if (scroll(ScrollDown, granularity, startNode, &stopNode, wheelEvent->deltaY(), roundedIntPoint(wheelEvent->absoluteLocation()))) wheelEvent->setDefaultHandled(); if (!m_latchedWheelEventNode) m_previousWheelScrolledNode = stopNode; } bool EventHandler::handleGestureShowPress() { m_lastShowPressTimestamp = WTF::currentTime(); FrameView* view = m_frame->view(); if (!view) return false; const FrameView::ScrollableAreaSet* areas = view->scrollableAreas(); if (!areas) return false; for (FrameView::ScrollableAreaSet::const_iterator it = areas->begin(); it != areas->end(); ++it) { ScrollableArea* sa = *it; ScrollAnimator* animator = sa->existingScrollAnimator(); if (animator) animator->cancelAnimations(); } return false; } bool EventHandler::handleGestureEvent(const PlatformGestureEvent& gestureEvent) { TRACE_EVENT0("input", "EventHandler::handleGestureEvent"); // Scrolling-related gesture events invoke EventHandler recursively for each frame down // the chain, doing a single-frame hit-test per frame. This matches handleWheelEvent. // Perhaps we could simplify things by rewriting scroll handling to work inner frame // out, and then unify with other gesture events. if (gestureEvent.isScrollEvent()) return handleGestureScrollEvent(gestureEvent); // Non-scrolling related gesture events instead do a single cross-frame hit-test and // jump directly to the inner most frame. This matches handleMousePressEvent etc. // Hit test across all frames and do touch adjustment as necessary for the event type. GestureEventWithHitTestResults targetedEvent = targetGestureEvent(gestureEvent); // Route to the correct frame. if (LocalFrame* innerFrame = targetedEvent.hitTestResult().innerNodeFrame()) return innerFrame->eventHandler().handleGestureEventInFrame(targetedEvent); // No hit test result, handle in root instance. Perhaps we should just return false instead? return handleGestureEventInFrame(targetedEvent); } bool EventHandler::handleGestureEventInFrame(const GestureEventWithHitTestResults& targetedEvent) { ASSERT(!targetedEvent.event().isScrollEvent()); RefPtr eventTarget = targetedEvent.hitTestResult().targetNode(); RefPtr scrollbar = targetedEvent.hitTestResult().scrollbar(); const PlatformGestureEvent& gestureEvent = targetedEvent.event(); if (scrollbar) { bool eventSwallowed = scrollbar->gestureEvent(gestureEvent); if (gestureEvent.type() == PlatformEvent::GestureTapDown && eventSwallowed) m_scrollbarHandlingScrollGesture = scrollbar; if (eventSwallowed) return true; } if (eventTarget && eventTarget->dispatchGestureEvent(gestureEvent)) return true; switch (gestureEvent.type()) { case PlatformEvent::GestureTwoFingerTap: // FIXME(sky): Remove this. case PlatformEvent::GestureTap: return handleGestureTap(targetedEvent); case PlatformEvent::GestureShowPress: return handleGestureShowPress(); case PlatformEvent::GestureLongPress: return handleGestureLongPress(targetedEvent); case PlatformEvent::GestureLongTap: return handleGestureLongTap(targetedEvent); case PlatformEvent::GestureTapDown: case PlatformEvent::GesturePinchBegin: case PlatformEvent::GesturePinchEnd: case PlatformEvent::GesturePinchUpdate: case PlatformEvent::GestureTapDownCancel: case PlatformEvent::GestureTapUnconfirmed: break; default: ASSERT_NOT_REACHED(); } return false; } bool EventHandler::handleGestureScrollEvent(const PlatformGestureEvent& gestureEvent) { RefPtr eventTarget = nullptr; RefPtr scrollbar; if (gestureEvent.type() != PlatformEvent::GestureScrollBegin) { scrollbar = m_scrollbarHandlingScrollGesture.get(); eventTarget = m_scrollGestureHandlingNode.get(); } if (!eventTarget) { Document* document = m_frame->document(); if (!document->renderView()) return false; LayoutPoint viewPoint = gestureEvent.position(); HitTestRequest request(HitTestRequest::ReadOnly); HitTestResult result(viewPoint); document->renderView()->hitTest(request, result); eventTarget = result.innerNode(); m_lastGestureScrollOverWidget = result.isOverWidget(); m_scrollGestureHandlingNode = eventTarget; m_previousGestureScrolledNode = nullptr; if (!scrollbar) scrollbar = result.scrollbar(); } if (scrollbar) { bool eventSwallowed = scrollbar->gestureEvent(gestureEvent); if (gestureEvent.type() == PlatformEvent::GestureScrollEnd || gestureEvent.type() == PlatformEvent::GestureFlingStart || !eventSwallowed) { m_scrollbarHandlingScrollGesture = nullptr; } if (eventSwallowed) return true; } if (eventTarget) { bool eventSwallowed = handleScrollGestureOnResizer(eventTarget.get(), gestureEvent); if (!eventSwallowed) eventSwallowed = eventTarget->dispatchGestureEvent(gestureEvent); if (eventSwallowed) return true; } switch (gestureEvent.type()) { case PlatformEvent::GestureScrollBegin: return handleGestureScrollBegin(gestureEvent); case PlatformEvent::GestureScrollUpdate: case PlatformEvent::GestureScrollUpdateWithoutPropagation: return handleGestureScrollUpdate(gestureEvent); case PlatformEvent::GestureScrollEnd: return handleGestureScrollEnd(gestureEvent); case PlatformEvent::GestureFlingStart: case PlatformEvent::GesturePinchBegin: case PlatformEvent::GesturePinchEnd: case PlatformEvent::GesturePinchUpdate: return false; default: ASSERT_NOT_REACHED(); return false; } } bool EventHandler::handleGestureTap(const GestureEventWithHitTestResults& targetedEvent) { RefPtr protector(m_frame->view()); const PlatformGestureEvent& gestureEvent = targetedEvent.event(); UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture); unsigned modifierFlags = 0; if (gestureEvent.altKey()) modifierFlags |= PlatformEvent::AltKey; if (gestureEvent.ctrlKey()) modifierFlags |= PlatformEvent::CtrlKey; if (gestureEvent.metaKey()) modifierFlags |= PlatformEvent::MetaKey; if (gestureEvent.shiftKey()) modifierFlags |= PlatformEvent::ShiftKey; PlatformEvent::Modifiers modifiers = static_cast(modifierFlags); HitTestResult currentHitTest = targetedEvent.hitTestResult(); // We use the adjusted position so the application isn't surprised to see a event with // co-ordinates outside the target's bounds. IntPoint adjustedPoint = gestureEvent.position(); PlatformMouseEvent fakeMouseMove(adjustedPoint, gestureEvent.globalPosition(), NoButton, PlatformEvent::MouseMoved, /* clickCount */ 0, modifiers, PlatformMouseEvent::FromTouch, gestureEvent.timestamp()); dispatchMouseEvent(EventTypeNames::mousemove, currentHitTest.innerNode(), 0, fakeMouseMove, true); // Do a new hit-test in case the mousemove event changed the DOM. // Note that if the original hit test wasn't over an element (eg. was over a scrollbar) we // don't want to re-hit-test because it may be in the wrong frame (and there's no way the page // could have seen the event anyway). // FIXME: Use a hit-test cache to avoid unnecessary hit tests. http://crbug.com/398920 if (currentHitTest.innerNode()) currentHitTest = hitTestResultInFrame(m_frame, adjustedPoint, HitTestRequest::ReadOnly); m_clickNode = currentHitTest.innerNode(); if (m_clickNode && m_clickNode->isTextNode()) m_clickNode = NodeRenderingTraversal::parent(m_clickNode.get()); PlatformMouseEvent fakeMouseDown(adjustedPoint, gestureEvent.globalPosition(), LeftButton, PlatformEvent::MousePressed, gestureEvent.tapCount(), modifiers, PlatformMouseEvent::FromTouch, gestureEvent.timestamp()); bool swallowMouseDownEvent = !dispatchMouseEvent(EventTypeNames::mousedown, currentHitTest.innerNode(), gestureEvent.tapCount(), fakeMouseDown, true); if (!swallowMouseDownEvent) swallowMouseDownEvent = handleMouseFocus(fakeMouseDown); if (!swallowMouseDownEvent) swallowMouseDownEvent = handleMousePressEvent(MouseEventWithHitTestResults(fakeMouseDown, currentHitTest)); // FIXME: Use a hit-test cache to avoid unnecessary hit tests. http://crbug.com/398920 if (currentHitTest.innerNode()) currentHitTest = hitTestResultInFrame(m_frame, adjustedPoint, HitTestRequest::ReadOnly); PlatformMouseEvent fakeMouseUp(adjustedPoint, gestureEvent.globalPosition(), LeftButton, PlatformEvent::MouseReleased, gestureEvent.tapCount(), modifiers, PlatformMouseEvent::FromTouch, gestureEvent.timestamp()); bool swallowMouseUpEvent = !dispatchMouseEvent(EventTypeNames::mouseup, currentHitTest.innerNode(), gestureEvent.tapCount(), fakeMouseUp, false); bool swallowClickEvent = false; if (m_clickNode) { if (currentHitTest.innerNode()) { Node* clickTargetNode = NodeRenderingTraversal::commonAncestor(*currentHitTest.innerNode(), *m_clickNode); swallowClickEvent = !dispatchMouseEvent(EventTypeNames::click, clickTargetNode, gestureEvent.tapCount(), fakeMouseUp, true); } m_clickNode = nullptr; } if (!swallowMouseUpEvent) swallowMouseUpEvent = handleMouseReleaseEvent(MouseEventWithHitTestResults(fakeMouseUp, currentHitTest)); return swallowMouseDownEvent | swallowMouseUpEvent | swallowClickEvent; } bool EventHandler::handleGestureLongPress(const GestureEventWithHitTestResults& targetedEvent) { const PlatformGestureEvent& gestureEvent = targetedEvent.event(); // FIXME: Ideally we should try to remove the extra mouse-specific hit-tests here (re-using the // supplied HitTestResult), but that will require some overhaul of the touch drag-and-drop code // and LongPress is such a special scenario that it's unlikely to matter much in practice. #if OS(ANDROID) bool shouldLongPressSelectWord = true; #else bool shouldLongPressSelectWord = m_frame->settings() && m_frame->settings()->touchEditingEnabled(); #endif if (shouldLongPressSelectWord) { IntPoint hitTestPoint = gestureEvent.position(); HitTestResult result = hitTestResultAtPoint(hitTestPoint); Node* innerNode = result.targetNode(); if (!result.isLiveLink() && innerNode && (innerNode->isContentEditable() || innerNode->isTextNode())) { selectClosestWordFromHitTestResult(result, DontAppendTrailingWhitespace); if (m_frame->selection().isRange()) { focusDocumentView(); return true; } } } return true; } bool EventHandler::handleGestureLongTap(const GestureEventWithHitTestResults& targetedEvent) { return false; } bool EventHandler::handleScrollGestureOnResizer(Node* eventTarget, const PlatformGestureEvent& gestureEvent) { if (gestureEvent.type() == PlatformEvent::GestureScrollBegin) { RenderLayer* layer = eventTarget->renderer() ? eventTarget->renderer()->enclosingLayer() : 0; IntPoint p = gestureEvent.position(); if (layer && layer->scrollableArea() && layer->scrollableArea()->isPointInResizeControl(p, ResizerForTouch)) { m_resizeScrollableArea = layer->scrollableArea(); m_resizeScrollableArea->setInResizeMode(true); m_offsetFromResizeCorner = m_resizeScrollableArea->offsetFromResizeCorner(p); return true; } } else if (gestureEvent.type() == PlatformEvent::GestureScrollUpdate || gestureEvent.type() == PlatformEvent::GestureScrollUpdateWithoutPropagation) { if (m_resizeScrollableArea && m_resizeScrollableArea->inResizeMode()) { m_resizeScrollableArea->resize(gestureEvent, m_offsetFromResizeCorner); return true; } } else if (gestureEvent.type() == PlatformEvent::GestureScrollEnd) { if (m_resizeScrollableArea && m_resizeScrollableArea->inResizeMode()) { m_resizeScrollableArea->setInResizeMode(false); m_resizeScrollableArea = 0; return false; } } return false; } bool EventHandler::passScrollGestureEventToWidget(const PlatformGestureEvent& gestureEvent, RenderObject* renderer) { ASSERT(gestureEvent.isScrollEvent()); // FIXME(sky): Remove this. return false; } bool EventHandler::handleGestureScrollEnd(const PlatformGestureEvent& gestureEvent) { RefPtr node = m_scrollGestureHandlingNode; clearGestureScrollNodes(); if (node) passScrollGestureEventToWidget(gestureEvent, node->renderer()); return false; } bool EventHandler::handleGestureScrollBegin(const PlatformGestureEvent& gestureEvent) { Document* document = m_frame->document(); if (!document->renderView()) return false; FrameView* view = m_frame->view(); if (!view) return false; // If there's no renderer on the node, send the event to the nearest ancestor with a renderer. // Needed for elements so we can touch scroll