blob: 9d5fe271b9132653fe997073ea6ee090d086be6f [file] [log] [blame]
/*
* 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