| /* |
| * 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 "sky/engine/config.h" |
| #include "sky/engine/core/page/EventHandler.h" |
| |
| #include "gen/sky/core/HTMLNames.h" |
| #include "gen/sky/platform/RuntimeEnabledFeatures.h" |
| #include "sky/engine/bindings/core/v8/ExceptionStatePlaceholder.h" |
| #include "sky/engine/core/dom/Document.h" |
| #include "sky/engine/core/dom/DocumentMarkerController.h" |
| #include "sky/engine/core/dom/NodeRenderingTraversal.h" |
| #include "sky/engine/core/dom/shadow/ShadowRoot.h" |
| #include "sky/engine/core/editing/Editor.h" |
| #include "sky/engine/core/editing/FrameSelection.h" |
| #include "sky/engine/core/editing/TextIterator.h" |
| #include "sky/engine/core/editing/htmlediting.h" |
| #include "sky/engine/core/events/DOMWindowEventQueue.h" |
| #include "sky/engine/core/events/EventPath.h" |
| #include "sky/engine/core/events/KeyboardEvent.h" |
| #include "sky/engine/core/events/TextEvent.h" |
| #include "sky/engine/core/fetch/ImageResource.h" |
| #include "sky/engine/core/frame/FrameView.h" |
| #include "sky/engine/core/frame/LocalFrame.h" |
| #include "sky/engine/core/frame/Settings.h" |
| #include "sky/engine/core/loader/FrameLoaderClient.h" |
| #include "sky/engine/core/page/AutoscrollController.h" |
| #include "sky/engine/core/page/Chrome.h" |
| #include "sky/engine/core/page/ChromeClient.h" |
| #include "sky/engine/core/page/EditorClient.h" |
| #include "sky/engine/core/page/EventWithHitTestResults.h" |
| #include "sky/engine/core/page/FocusController.h" |
| #include "sky/engine/core/page/Page.h" |
| #include "sky/engine/core/rendering/HitTestRequest.h" |
| #include "sky/engine/core/rendering/HitTestResult.h" |
| #include "sky/engine/core/rendering/RenderLayer.h" |
| #include "sky/engine/core/rendering/RenderView.h" |
| #include "sky/engine/core/rendering/style/RenderStyle.h" |
| #include "sky/engine/platform/PlatformGestureEvent.h" |
| #include "sky/engine/platform/PlatformKeyboardEvent.h" |
| #include "sky/engine/platform/TraceEvent.h" |
| #include "sky/engine/platform/WindowsKeyboardCodes.h" |
| #include "sky/engine/platform/geometry/FloatPoint.h" |
| #include "sky/engine/platform/graphics/Image.h" |
| #include "sky/engine/platform/heap/Handle.h" |
| #include "sky/engine/platform/scroll/ScrollAnimator.h" |
| #include "sky/engine/platform/scroll/Scrollbar.h" |
| #include "sky/engine/wtf/Assertions.h" |
| #include "sky/engine/wtf/CurrentTime.h" |
| #include "sky/engine/wtf/StdLibExtras.h" |
| #include "sky/engine/wtf/TemporaryChange.h" |
| |
| namespace blink { |
| |
| // 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; |
| }; |
| |
| EventHandler::EventHandler(LocalFrame* frame) |
| : m_frame(frame) |
| , m_mousePressed(false) |
| , m_capturesDragging(false) |
| , m_mouseDownMayStartSelect(false) |
| , m_mouseDownMayStartDrag(false) |
| , m_selectionInitiationState(HaveNotStartedSelection) |
| , m_hoverTimer(this, &EventHandler::hoverTimerFired) |
| , m_cursorUpdateTimer(this, &EventHandler::cursorUpdateTimerFired) |
| , m_mouseDownMayStartAutoscroll(false) |
| , m_clickCount(0) |
| , m_shouldOnlyFireDragOverEvent(false) |
| , m_mousePositionIsUnknown(true) |
| , m_scrollGestureHandlingNode(nullptr) |
| , m_lastGestureScrollOverWidget(false) |
| , m_maxMouseMovedDuration(0) |
| , m_didStartDrag(false) |
| , m_activeIntervalTimer(this, &EventHandler::activeIntervalTimerFired) |
| , m_lastShowPressTimestamp(0) |
| { |
| } |
| |
| EventHandler::~EventHandler() |
| { |
| } |
| |
| void EventHandler::clear() |
| { |
| m_hoverTimer.stop(); |
| m_cursorUpdateTimer.stop(); |
| m_activeIntervalTimer.stop(); |
| 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_mousePressNode = nullptr; |
| m_mousePressed = false; |
| m_capturesDragging = false; |
| m_previousWheelScrolledNode = nullptr; |
| m_scrollGestureHandlingNode = nullptr; |
| m_lastGestureScrollOverWidget = false; |
| m_previousGestureScrolledNode = nullptr; |
| m_scrollbarHandlingScrollGesture = nullptr; |
| m_maxMouseMovedDuration = 0; |
| m_didStartDrag = 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 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::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); |
| } |
| |
| 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); |
| m_frame->contentRenderer()->hitTest(request, result); |
| |
| 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()->updateLayout(); |
| 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<EventHandler>*) |
| { |
| 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) |
| { |
| 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(); |
| |
| // 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()) { |
| return iBeam; |
| } |
| |
| RenderObject* renderer = node ? node->renderer() : 0; |
| if ((editable || (renderer && renderer->isText() && node->canStartSelection())) && !result.scrollbar()) |
| return iBeam; |
| return pointerCursor(); |
| } |
| |
| ScrollableArea* EventHandler::associatedScrollableArea(const RenderLayer* layer) const |
| { |
| if (RenderLayerScrollableArea* scrollableArea = layer->scrollableArea()) { |
| if (scrollableArea->scrollsOverflow()) |
| return scrollableArea; |
| } |
| |
| return 0; |
| } |
| |
| void EventHandler::invalidateClick() |
| { |
| m_clickCount = 0; |
| m_clickNode = nullptr; |
| } |
| |
| 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::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<Node> eventTarget = targetedEvent.hitTestResult().targetNode(); |
| RefPtr<Scrollbar> 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<Node> eventTarget = nullptr; |
| RefPtr<Scrollbar> 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 && eventTarget->dispatchGestureEvent(gestureEvent)) |
| 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) |
| { |
| return false; |
| } |
| |
| 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. |
| |
| 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::passScrollGestureEventToWidget(const PlatformGestureEvent& gestureEvent, RenderObject* renderer) |
| { |
| ASSERT(gestureEvent.isScrollEvent()); |
| |
| // FIXME(sky): Remove this. |
| return false; |
| } |
| |
| bool EventHandler::handleGestureScrollEnd(const PlatformGestureEvent& gestureEvent) { |
| RefPtr<Node> 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 <option> and <optgroup> elements so we can touch scroll <select>s |
| while (m_scrollGestureHandlingNode && !m_scrollGestureHandlingNode->renderer()) |
| m_scrollGestureHandlingNode = m_scrollGestureHandlingNode->parentOrShadowHostNode(); |
| |
| if (!m_scrollGestureHandlingNode) |
| return false; |
| |
| passScrollGestureEventToWidget(gestureEvent, m_scrollGestureHandlingNode->renderer()); |
| |
| return true; |
| } |
| |
| bool EventHandler::handleGestureScrollUpdate(const PlatformGestureEvent& gestureEvent) |
| { |
| FloatSize delta(gestureEvent.deltaX(), gestureEvent.deltaY()); |
| if (delta.isZero()) |
| return false; |
| |
| Node* node = m_scrollGestureHandlingNode.get(); |
| if (!node) |
| return sendScrollEventToView(gestureEvent, delta); |
| |
| // Ignore this event if the targeted node does not have a valid renderer. |
| RenderObject* renderer = node->renderer(); |
| if (!renderer) |
| return false; |
| |
| RefPtr<FrameView> protector(m_frame->view()); |
| |
| Node* stopNode = 0; |
| bool scrollShouldNotPropagate = gestureEvent.type() == PlatformEvent::GestureScrollUpdateWithoutPropagation; |
| |
| // Try to send the event to the correct view. |
| if (passScrollGestureEventToWidget(gestureEvent, renderer)) { |
| if(scrollShouldNotPropagate) |
| m_previousGestureScrolledNode = m_scrollGestureHandlingNode; |
| |
| return true; |
| } |
| |
| if (scrollShouldNotPropagate) |
| stopNode = m_previousGestureScrolledNode.get(); |
| |
| // First try to scroll the closest scrollable RenderBox ancestor of |node|. |
| ScrollGranularity granularity = ScrollByPixel; |
| bool horizontalScroll = scroll(ScrollLeft, granularity, node, &stopNode, delta.width()); |
| bool verticalScroll = scroll(ScrollUp, granularity, node, &stopNode, delta.height()); |
| |
| if (scrollShouldNotPropagate) |
| m_previousGestureScrolledNode = stopNode; |
| |
| if (horizontalScroll || verticalScroll) { |
| return true; |
| } |
| |
| // Otherwise try to scroll the view. |
| return sendScrollEventToView(gestureEvent, delta); |
| } |
| |
| bool EventHandler::sendScrollEventToView(const PlatformGestureEvent& gestureEvent, const FloatSize& scaledDelta) |
| { |
| return false; |
| } |
| |
| void EventHandler::clearGestureScrollNodes() |
| { |
| m_scrollGestureHandlingNode = nullptr; |
| m_previousGestureScrolledNode = nullptr; |
| } |
| |
| bool EventHandler::isScrollbarHandlingGestures() const |
| { |
| return m_scrollbarHandlingScrollGesture.get(); |
| } |
| |
| GestureEventWithHitTestResults EventHandler::targetGestureEvent(const PlatformGestureEvent& gestureEvent, bool readOnly) |
| { |
| // Scrolling events get hit tested per frame (like wheel events do). |
| ASSERT(!gestureEvent.isScrollEvent()); |
| |
| HitTestRequest::HitTestRequestType hitType = getHitTypeForGestureType(gestureEvent.type()); |
| double activeInterval = 0; |
| bool shouldKeepActiveForMinInterval = false; |
| if (readOnly) { |
| hitType |= HitTestRequest::ReadOnly; |
| } else if (gestureEvent.type() == PlatformEvent::GestureTap) { |
| // If the Tap is received very shortly after ShowPress, we want to |
| // delay clearing of the active state so that it's visible to the user |
| // for at least a couple of frames. |
| activeInterval = WTF::currentTime() - m_lastShowPressTimestamp; |
| shouldKeepActiveForMinInterval = m_lastShowPressTimestamp && activeInterval < minimumActiveInterval; |
| if (shouldKeepActiveForMinInterval) |
| hitType |= HitTestRequest::ReadOnly; |
| } |
| |
| // Perform the rect-based hit-test. Note that we don't yet apply hover/active state here |
| // because we need to resolve touch adjustment first so that we apply hover/active it to |
| // the final adjusted node. |
| IntPoint hitTestPoint = gestureEvent.position(); |
| IntSize touchRadius = gestureEvent.area(); |
| touchRadius.scale(1.f / 2); |
| // FIXME: We should not do a rect-based hit-test if touch adjustment is disabled. |
| HitTestResult hitTestResult = hitTestResultAtPoint(hitTestPoint, hitType | HitTestRequest::ReadOnly, touchRadius); |
| |
| // Now apply hover/active state to the final target. |
| // FIXME: This is supposed to send mouseenter/mouseleave events, but doesn't because we |
| // aren't passing a PlatformMouseEvent. |
| HitTestRequest request(hitType); |
| |
| if (shouldKeepActiveForMinInterval) { |
| m_lastDeferredTapElement = hitTestResult.innerElement(); |
| m_activeIntervalTimer.startOneShot(minimumActiveInterval - activeInterval, FROM_HERE); |
| } |
| |
| return GestureEventWithHitTestResults(gestureEvent, hitTestResult); |
| } |
| |
| HitTestRequest::HitTestRequestType EventHandler::getHitTypeForGestureType(PlatformEvent::Type type) |
| { |
| HitTestRequest::HitTestRequestType hitType = HitTestRequest::ReadOnly; |
| switch (type) { |
| case PlatformEvent::GestureShowPress: |
| case PlatformEvent::GestureTapUnconfirmed: |
| return hitType | HitTestRequest::Active; |
| case PlatformEvent::GestureTapDownCancel: |
| // A TapDownCancel received when no element is active shouldn't really be changing hover state. |
| if (!m_frame->document()->activeHoverElement()) |
| hitType |= HitTestRequest::ReadOnly; |
| return hitType | HitTestRequest::Release; |
| case PlatformEvent::GestureTap: |
| return hitType | HitTestRequest::Release; |
| case PlatformEvent::GestureTapDown: |
| case PlatformEvent::GestureLongPress: |
| case PlatformEvent::GestureLongTap: |
| case PlatformEvent::GestureTwoFingerTap: |
| // FIXME: Shouldn't LongTap and TwoFingerTap clear the Active state? |
| return hitType | HitTestRequest::Active | HitTestRequest::ReadOnly; |
| default: |
| ASSERT_NOT_REACHED(); |
| return hitType | HitTestRequest::Active | HitTestRequest::ReadOnly; |
| } |
| } |
| |
| void EventHandler::scheduleHoverStateUpdate() |
| { |
| if (!m_hoverTimer.isActive()) |
| m_hoverTimer.startOneShot(0, FROM_HERE); |
| } |
| |
| void EventHandler::scheduleCursorUpdate() |
| { |
| if (!m_cursorUpdateTimer.isActive()) |
| m_cursorUpdateTimer.startOneShot(cursorUpdateInterval, FROM_HERE); |
| } |
| |
| bool EventHandler::isCursorVisible() const |
| { |
| return m_frame->page()->isCursorVisible(); |
| } |
| |
| void EventHandler::hoverTimerFired(Timer<EventHandler>*) |
| { |
| m_hoverTimer.stop(); |
| |
| ASSERT(m_frame); |
| ASSERT(m_frame->document()); |
| |
| if (RenderView* renderer = m_frame->contentRenderer()) { |
| HitTestRequest request(HitTestRequest::Move); |
| HitTestResult result(m_lastKnownMousePosition); |
| renderer->hitTest(request, result); |
| } |
| } |
| |
| void EventHandler::activeIntervalTimerFired(Timer<EventHandler>*) |
| { |
| m_activeIntervalTimer.stop(); |
| m_lastDeferredTapElement = nullptr; |
| } |
| |
| void EventHandler::notifyElementActivated() |
| { |
| // Since another element has been set to active, stop current timer and clear reference. |
| if (m_activeIntervalTimer.isActive()) |
| m_activeIntervalTimer.stop(); |
| m_lastDeferredTapElement = nullptr; |
| } |
| |
| bool EventHandler::keyEvent(const PlatformKeyboardEvent& initialKeyEvent) |
| { |
| RefPtr<FrameView> protector(m_frame->view()); |
| |
| if (initialKeyEvent.windowsVirtualKeyCode() == VK_CAPITAL) |
| capsLockStateMayHaveChanged(); |
| |
| // Check for cases where we are too early for events -- possible unmatched key up |
| // from pressing return in the location bar. |
| RefPtr<Node> node = eventTargetNodeForDocument(m_frame->document()); |
| if (!node) |
| return false; |
| |
| // FIXME: it would be fair to let an input method handle KeyUp events before DOM dispatch. |
| if (initialKeyEvent.type() == PlatformEvent::KeyUp || initialKeyEvent.type() == PlatformEvent::Char) |
| return !node->dispatchKeyEvent(initialKeyEvent); |
| |
| PlatformKeyboardEvent keyDownEvent = initialKeyEvent; |
| if (keyDownEvent.type() != PlatformEvent::RawKeyDown) |
| keyDownEvent.disambiguateKeyDownEvent(PlatformEvent::RawKeyDown); |
| RefPtr<KeyboardEvent> keydown = KeyboardEvent::create(keyDownEvent, m_frame->document()->domWindow()); |
| |
| keydown->setTarget(node); |
| |
| if (initialKeyEvent.type() == PlatformEvent::RawKeyDown) { |
| node->dispatchEvent(keydown, IGNORE_EXCEPTION); |
| // If frame changed as a result of keydown dispatch, then return true to avoid sending a subsequent keypress message to the new frame. |
| bool changedFocusedFrame = m_frame->page() && m_frame != m_frame->page()->focusController().focusedOrMainFrame(); |
| return keydown->defaultHandled() || keydown->defaultPrevented() || changedFocusedFrame; |
| } |
| |
| node->dispatchEvent(keydown, IGNORE_EXCEPTION); |
| // If frame changed as a result of keydown dispatch, then return early to avoid sending a subsequent keypress message to the new frame. |
| bool changedFocusedFrame = m_frame->page() && m_frame != m_frame->page()->focusController().focusedOrMainFrame(); |
| bool keydownResult = keydown->defaultHandled() || keydown->defaultPrevented() || changedFocusedFrame; |
| if (keydownResult) |
| return keydownResult; |
| |
| // Focus may have changed during keydown handling, so refetch node. |
| // But if we are dispatching a fake backward compatibility keypress, then we pretend that the keypress happened on the original node. |
| node = eventTargetNodeForDocument(m_frame->document()); |
| if (!node) |
| return false; |
| |
| PlatformKeyboardEvent keyPressEvent = initialKeyEvent; |
| keyPressEvent.disambiguateKeyDownEvent(PlatformEvent::Char); |
| if (keyPressEvent.text().isEmpty()) |
| return keydownResult; |
| RefPtr<KeyboardEvent> keypress = KeyboardEvent::create(keyPressEvent, m_frame->document()->domWindow()); |
| keypress->setTarget(node); |
| if (keydownResult) |
| keypress->setDefaultPrevented(true); |
| node->dispatchEvent(keypress, IGNORE_EXCEPTION); |
| |
| return keydownResult || keypress->defaultPrevented() || keypress->defaultHandled(); |
| } |
| |
| void EventHandler::defaultKeyboardEventHandler(KeyboardEvent* event) |
| { |
| if (event->type() == EventTypeNames::keydown) { |
| // Clear caret blinking suspended state to make sure that caret blinks |
| // when we type again after long pressing on an empty input field. |
| if (m_frame && m_frame->selection().isCaretBlinkingSuspended()) |
| m_frame->selection().setCaretBlinkingSuspended(false); |
| |
| m_frame->editor().handleKeyboardEvent(event); |
| if (event->defaultHandled()) |
| return; |
| if (event->keyIdentifier() == "U+0009") |
| defaultTabEventHandler(event); |
| } |
| if (event->type() == EventTypeNames::keypress) { |
| m_frame->editor().handleKeyboardEvent(event); |
| if (event->defaultHandled()) |
| return; |
| } |
| } |
| |
| bool EventHandler::dragHysteresisExceeded(const FloatPoint& floatDragViewportLocation) const |
| { |
| return dragHysteresisExceeded(flooredIntPoint(floatDragViewportLocation)); |
| } |
| |
| bool EventHandler::dragHysteresisExceeded(const IntPoint& dragViewportLocation) const |
| { |
| FrameView* view = m_frame->view(); |
| if (!view) |
| return false; |
| IntPoint dragLocation = dragViewportLocation; |
| IntSize delta = dragLocation - m_mouseDownPos; |
| |
| int threshold = 3; |
| |
| return abs(delta.width()) >= threshold || abs(delta.height()) >= threshold; |
| } |
| |
| bool EventHandler::handleTextInputEvent(const String& text, Event* underlyingEvent, TextEventInputType inputType) |
| { |
| // Platforms should differentiate real commands like selectAll from text input in disguise (like insertNewline), |
| // and avoid dispatching text input events from keydown default handlers. |
| ASSERT(!underlyingEvent || !underlyingEvent->isKeyboardEvent() || toKeyboardEvent(underlyingEvent)->type() == EventTypeNames::keypress); |
| |
| if (!m_frame) |
| return false; |
| |
| EventTarget* target; |
| if (underlyingEvent) |
| target = underlyingEvent->target(); |
| else |
| target = eventTargetNodeForDocument(m_frame->document()); |
| if (!target) |
| return false; |
| |
| RefPtr<TextEvent> event = TextEvent::create(m_frame->domWindow(), text, inputType); |
| event->setUnderlyingEvent(underlyingEvent); |
| |
| target->dispatchEvent(event, IGNORE_EXCEPTION); |
| return event->defaultHandled(); |
| } |
| |
| void EventHandler::defaultTextInputEventHandler(TextEvent* event) |
| { |
| if (m_frame->editor().handleTextEvent(event)) |
| event->setDefaultHandled(); |
| } |
| |
| void EventHandler::defaultTabEventHandler(KeyboardEvent* event) |
| { |
| } |
| |
| void EventHandler::capsLockStateMayHaveChanged() |
| { |
| } |
| |
| // If scrollbar (under mouse) is different from last, send a mouse exited. Set |
| // last to scrollbar if setLast is true; else set last to 0. |
| void EventHandler::updateLastScrollbarUnderMouse(Scrollbar* scrollbar, bool setLast) |
| { |
| if (m_lastScrollbarUnderMouse != scrollbar) { |
| // Send mouse exited to the old scrollbar. |
| if (m_lastScrollbarUnderMouse) |
| m_lastScrollbarUnderMouse->mouseExited(); |
| |
| // Send mouse entered if we're setting a new scrollbar. |
| if (scrollbar && setLast) |
| scrollbar->mouseEntered(); |
| |
| m_lastScrollbarUnderMouse = setLast ? scrollbar : 0; |
| } |
| } |
| |
| HitTestResult EventHandler::hitTestResultInFrame(LocalFrame* frame, const LayoutPoint& point, HitTestRequest::HitTestRequestType hitType) |
| { |
| HitTestResult result(point); |
| |
| if (!frame || !frame->contentRenderer()) |
| return result; |
| if (frame->view()) { |
| IntRect rect = frame->view()->visibleContentRect(); |
| if (!rect.contains(roundedIntPoint(point))) |
| return result; |
| } |
| frame->contentRenderer()->hitTest(HitTestRequest(hitType), result); |
| return result; |
| } |
| |
| TouchAction EventHandler::intersectTouchAction(TouchAction action1, TouchAction action2) |
| { |
| if (action1 == TouchActionNone || action2 == TouchActionNone) |
| return TouchActionNone; |
| if (action1 == TouchActionAuto) |
| return action2; |
| if (action2 == TouchActionAuto) |
| return action1; |
| if (!(action1 & action2)) |
| return TouchActionNone; |
| return action1 & action2; |
| } |
| |
| TouchAction EventHandler::computeEffectiveTouchAction(const Node& node) |
| { |
| // Start by permitting all actions, then walk the elements supporting |
| // touch-action from the target node up to the nearest scrollable ancestor |
| // and exclude any prohibited actions. |
| TouchAction effectiveTouchAction = TouchActionAuto; |
| for (const Node* curNode = &node; curNode; curNode = NodeRenderingTraversal::parent(curNode)) { |
| if (RenderObject* renderer = curNode->renderer()) { |
| if (renderer->supportsTouchAction()) { |
| TouchAction action = renderer->style()->touchAction(); |
| effectiveTouchAction = intersectTouchAction(action, effectiveTouchAction); |
| if (effectiveTouchAction == TouchActionNone) |
| break; |
| } |
| |
| // If we've reached an ancestor that supports a touch action, search no further. |
| if (renderer->isBox() && toRenderBox(renderer)->scrollsOverflow()) |
| break; |
| } |
| } |
| return effectiveTouchAction; |
| } |
| |
| void EventHandler::focusDocumentView() |
| { |
| Page* page = m_frame->page(); |
| if (!page) |
| return; |
| page->focusController().focusDocumentView(m_frame); |
| } |
| |
| } // namespace blink |