blob: c0466e38edf8f049f52f8cc888c43ae3f00f1842 [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 "config.h"
#include "core/page/EventHandler.h"
#include "HTMLNames.h"
#include "RuntimeEnabledFeatures.h"
#include "SVGNames.h"
#include "bindings/v8/ExceptionStatePlaceholder.h"
#include "core/dom/Clipboard.h"
#include "core/dom/Document.h"
#include "core/dom/DocumentMarkerController.h"
#include "core/dom/FullscreenElementStack.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/ThreadLocalEventNames.h"
#include "core/events/TouchEvent.h"
#include "core/events/WheelEvent.h"
#include "core/fetch/ImageResource.h"
#include "core/html/HTMLDialogElement.h"
#include "core/html/HTMLFrameElementBase.h"
#include "core/html/HTMLFrameSetElement.h"
#include "core/html/HTMLInputElement.h"
#include "core/loader/FrameLoader.h"
#include "core/loader/FrameLoaderClient.h"
#include "core/page/AutoscrollController.h"
#include "core/page/BackForwardClient.h"
#include "core/page/Chrome.h"
#include "core/page/ChromeClient.h"
#include "core/page/DragController.h"
#include "core/page/DragState.h"
#include "core/page/EditorClient.h"
#include "core/page/FocusController.h"
#include "core/frame/Frame.h"
#include "core/page/FrameTree.h"
#include "core/frame/FrameView.h"
#include "core/inspector/InspectorController.h"
#include "core/page/MouseEventWithHitTestResults.h"
#include "core/page/Page.h"
#include "core/page/Settings.h"
#include "core/page/SpatialNavigation.h"
#include "core/page/TouchAdjustment.h"
#include "core/platform/chromium/ChromiumDataObject.h"
#include "core/rendering/HitTestRequest.h"
#include "core/rendering/HitTestResult.h"
#include "core/rendering/RenderFlowThread.h"
#include "core/rendering/RenderLayer.h"
#include "core/rendering/RenderRegion.h"
#include "core/rendering/RenderTextControlSingleLine.h"
#include "core/rendering/RenderView.h"
#include "core/rendering/RenderWidget.h"
#include "core/rendering/style/CursorList.h"
#include "core/rendering/style/RenderStyle.h"
#include "core/svg/SVGDocument.h"
#include "core/svg/SVGElementInstance.h"
#include "core/svg/SVGUseElement.h"
#include "platform/PlatformGestureEvent.h"
#include "platform/PlatformKeyboardEvent.h"
#include "platform/PlatformTouchEvent.h"
#include "platform/PlatformWheelEvent.h"
#include "platform/WindowsKeyboardCodes.h"
#include "platform/geometry/FloatPoint.h"
#include "platform/graphics/Image.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 WebCore {
using namespace HTMLNames;
using namespace SVGNames;
// The link drag hysteresis is much larger than the others because there
// needs to be enough space to cancel the link press without starting a link drag,
// and because dragging links is rare.
static const int LinkDragHysteresis = 40;
static const int ImageDragHysteresis = 5;
static const int TextDragHysteresis = 3;
static const int GeneralDragHysteresis = 3;
// 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 2 frames, which should be long enough to be noticeable.
static const double minimumActiveInterval = 0.032;
#if OS(MACOSX)
static const double TextDragDelay = 0.15;
#else
static const double TextDragDelay = 0.0;
#endif
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;
};
class SyntheticTouchPoint : public PlatformTouchPoint {
public:
// The default values are based on http://dvcs.w3.org/hg/webevents/raw-file/tip/touchevents.html
explicit SyntheticTouchPoint(const PlatformMouseEvent& event)
{
const static int idDefaultValue = 0;
const static int radiusYDefaultValue = 1;
const static int radiusXDefaultValue = 1;
const static float rotationAngleDefaultValue = 0.0f;
const static float forceDefaultValue = 1.0f;
m_id = idDefaultValue; // There is only one active TouchPoint.
m_screenPos = event.globalPosition();
m_pos = event.position();
m_radiusY = radiusYDefaultValue;
m_radiusX = radiusXDefaultValue;
m_rotationAngle = rotationAngleDefaultValue;
m_force = forceDefaultValue;
PlatformEvent::Type type = event.type();
ASSERT(type == PlatformEvent::MouseMoved || type == PlatformEvent::MousePressed || type == PlatformEvent::MouseReleased);
switch (type) {
case PlatformEvent::MouseMoved:
m_state = TouchMoved;
break;
case PlatformEvent::MousePressed:
m_state = TouchPressed;
break;
case PlatformEvent::MouseReleased:
m_state = TouchReleased;
break;
default:
ASSERT_NOT_REACHED();
break;
}
}
};
class SyntheticSingleTouchEvent : public PlatformTouchEvent {
public:
explicit SyntheticSingleTouchEvent(const PlatformMouseEvent& event)
{
switch (event.type()) {
case PlatformEvent::MouseMoved:
m_type = TouchMove;
break;
case PlatformEvent::MousePressed:
m_type = TouchStart;
break;
case PlatformEvent::MouseReleased:
m_type = TouchEnd;
break;
default:
ASSERT_NOT_REACHED();
m_type = NoType;
break;
}
m_timestamp = event.timestamp();
m_modifiers = event.modifiers();
m_touchPoints.append(SyntheticTouchPoint(event));
}
};
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;
}
}
static inline bool scrollNodeLogically(float delta, ScrollGranularity granularity, ScrollLogicalDirection direction, Node* node, Node** stopNode)
{
if (!delta)
return false;
if (!node->renderer())
return false;
RenderBox* curBox = node->renderer()->enclosingBox();
while (curBox && !curBox->isRenderView()) {
ScrollDirection physicalDirection =
logicalToPhysical(direction, curBox->isHorizontalWritingMode(), curBox->style()->isFlippedBlocksWritingMode());
if (curBox->scroll(physicalDirection, granularity, delta)) {
if (stopNode)
*stopNode = curBox->node();
return true;
}
if (stopNode && *stopNode && curBox->node() == *stopNode)
return true;
curBox = curBox->containingBlock();
}
return false;
}
static inline bool scrollNode(float delta, ScrollGranularity granularity, ScrollDirection positiveDirection, ScrollDirection negativeDirection, Node* node, Node** stopNode, IntPoint absolutePoint = IntPoint())
{
if (!delta)
return false;
if (!node->renderer())
return false;
float absDelta = delta > 0 ? delta : -delta;
RenderBox* curBox = node->renderer()->enclosingBox();
while (curBox && !curBox->isRenderView()) {
if (curBox->scroll(delta < 0 ? negativeDirection : positiveDirection, granularity, absDelta)) {
if (stopNode)
*stopNode = curBox->node();
return true;
}
if (stopNode && *stopNode && curBox->node() == *stopNode)
return true;
// FIXME: This should probably move to a virtual method on RenderBox, something like RenderBox::scrollAncestor, and specialized for RenderFlowThread
curBox = curBox->containingBlock();
if (curBox && curBox->isRenderNamedFlowThread()) {
RenderBox* flowedBox = curBox;
if (RenderBox* startBox = node->renderBox())
flowedBox = startBox;
curBox = toRenderFlowThread(curBox)->regionFromAbsolutePointAndBox(absolutePoint, flowedBox);
}
}
return false;
}
// Refetch the event target node if it is removed or currently is the shadow node inside an <input> element.
// If a mouse event handler changes the input element type to one that has a widget associated,
// we'd like to EventHandler::handleMousePressEvent to pass the event to the widget and thus the
// event target node can't still be the shadow node.
static inline bool shouldRefetchEventTarget(const MouseEventWithHitTestResults& mev)
{
Node* targetNode = mev.targetNode();
if (!targetNode || !targetNode->parentNode())
return true;
return targetNode->isShadowRoot() && toShadowRoot(targetNode)->host()->hasTagName(inputTag);
}
EventHandler::EventHandler(Frame* frame)
: m_frame(frame)
, m_mousePressed(false)
, m_capturesDragging(false)
, m_mouseDownMayStartSelect(false)
, m_mouseDownMayStartDrag(false)
, m_mouseDownWasSingleClickInSelection(false)
, m_selectionInitiationState(HaveNotStartedSelection)
, m_panScrollButtonPressed(false)
, m_hoverTimer(this, &EventHandler::hoverTimerFired)
, m_cursorUpdateTimer(this, &EventHandler::cursorUpdateTimerFired)
, m_mouseDownMayStartAutoscroll(false)
, m_mouseDownWasInSubframe(false)
, m_fakeMouseMoveEventTimer(this, &EventHandler::fakeMouseMoveEventTimerFired)
, m_svgPan(false)
, m_resizeScrollableArea(0)
, m_eventHandlerWillResetCapturingMouseEventsNode(0)
, m_clickCount(0)
, m_shouldOnlyFireDragOverEvent(false)
, m_mousePositionIsUnknown(true)
, m_mouseDownTimestamp(0)
, m_widgetIsLatched(false)
, m_originatingTouchPointTargetKey(0)
, m_touchPressed(false)
, m_scrollGestureHandlingNode(0)
, m_lastHitTestResultOverWidget(false)
, m_maxMouseMovedDuration(0)
, m_baseEventType(PlatformEvent::NoType)
, m_didStartDrag(false)
, m_longTapShouldInvokeContextMenu(false)
, m_syntheticPageScaleFactor(0)
, m_activeIntervalTimer(this, &EventHandler::activeIntervalTimerFired)
, m_lastShowPressTimestamp(0)
, m_shouldKeepActiveForMinInterval(false)
{
}
EventHandler::~EventHandler()
{
ASSERT(!m_fakeMouseMoveEventTimer.isActive());
}
DragState& EventHandler::dragState()
{
DEFINE_STATIC_LOCAL(DragState, state, ());
return state;
}
void EventHandler::clear()
{
m_hoverTimer.stop();
m_cursorUpdateTimer.stop();
m_fakeMouseMoveEventTimer.stop();
m_activeIntervalTimer.stop();
m_resizeScrollableArea = 0;
m_nodeUnderMouse = 0;
m_lastNodeUnderMouse = 0;
m_instanceUnderMouse = 0;
m_lastInstanceUnderMouse = 0;
m_lastMouseMoveEventSubframe = 0;
m_lastScrollbarUnderMouse = 0;
m_clickCount = 0;
m_clickNode = 0;
m_frameSetBeingResized = 0;
m_dragTarget = 0;
m_shouldOnlyFireDragOverEvent = false;
m_mousePositionIsUnknown = true;
m_lastKnownMousePosition = IntPoint();
m_lastKnownMouseGlobalPosition = IntPoint();
m_lastMouseDownUserGestureToken.clear();
m_mousePressNode = 0;
m_mousePressed = false;
m_capturesDragging = false;
m_capturingMouseEventsNode = 0;
m_latchedWheelEventNode = 0;
m_previousWheelScrolledNode = 0;
m_originatingTouchPointTargets.clear();
m_originatingTouchPointDocument.clear();
m_originatingTouchPointTargetKey = 0;
m_scrollGestureHandlingNode = 0;
m_lastHitTestResultOverWidget = false;
m_previousGestureScrolledNode = 0;
m_scrollbarHandlingScrollGesture = 0;
m_maxMouseMovedDuration = 0;
m_baseEventType = PlatformEvent::NoType;
m_didStartDrag = false;
m_touchPressed = false;
m_mouseDownMayStartSelect = false;
m_mouseDownMayStartDrag = false;
m_lastShowPressTimestamp = 0;
m_shouldKeepActiveForMinInterval = false;
m_lastDeferredTapElement = 0;
}
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 = 0;
}
}
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()) {
Vector<DocumentMarker*> 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(),
(result.event().clickCount() == 2 && m_frame->editor().isSelectTrailingWhitespaceEnabled()) ? ShouldAppendTrailingWhitespace : DontAppendTrailingWhitespace);
}
}
void EventHandler::selectClosestMisspellingFromMouseEvent(const MouseEventWithHitTestResults& result)
{
if (m_mouseDownMayStartSelect) {
selectClosestMisspellingFromHitTestResult(result.hitTestResult(),
(result.event().clickCount() == 2 && m_frame->editor().isSelectTrailingWhitespaceEnabled()) ? ShouldAppendTrailingWhitespace : 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)
{
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)
{
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 = Range::create(*start.document(), start, end);
return TextIterator::rangeLength(range.get(), true);
}
bool EventHandler::handleMousePressEventSingleClick(const MouseEventWithHitTestResults& event)
{
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.
if (FrameView* view = m_frame->view()) {
LayoutPoint vPoint = view->windowToContents(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(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()) {
// See <rdar://problem/3668157> 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, visiblePos);
bool handled = updateSelectionForMouseDownDispatchingSelectStart(innerNode, newSelection, granularity);
return handled;
}
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)
{
// Reset drag state.
dragState().m_dragSrc = 0;
cancelFakeMouseMoveEvent();
m_frame->document()->updateLayoutIgnorePendingStylesheets();
if (ScrollView* scrollView = m_frame->view()) {
if (scrollView->isPointInScrollbarCorner(event.event().position()))
return false;
}
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;
if (m_frame->document()->isSVGDocument()
&& toSVGDocument(m_frame->document())->zoomAndPanEnabled()) {
if (event.event().shiftKey() && singleClick) {
m_svgPan = true;
toSVGDocument(m_frame->document())->startPan(m_frame->view()->windowToContents(event.event().position()));
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)
{
if (!m_mousePressed)
return false;
if (handleDrag(event, ShouldCheckDragHysteresis))
return true;
Node* targetNode = event.targetNode();
if (event.event().button() != LeftButton || !targetNode)
return false;
RenderObject* renderer = targetNode->renderer();
if (!renderer) {
Node* parent = EventPath::parent(targetNode);
if (!parent)
return false;
renderer = parent->renderer();
if (!renderer || !renderer->isListBox())
return false;
}
m_mouseDownMayStartDrag = false;
if (m_mouseDownMayStartAutoscroll && !panScrollInProgress()) {
if (AutoscrollController* controller = autoscrollController()) {
controller->startAutoscrollForSelection(renderer);
m_mouseDownMayStartAutoscroll = false;
}
}
if (m_selectionInitiationState != ExtendedSelection) {
HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::ConfusingAndOftenMisusedDisallowShadowContent);
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 | HitTestRequest::ConfusingAndOftenMisusedDisallowShadowContent);
HitTestResult result(view->windowToContents(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();
// Special case to limit selection to the containing block for SVG text.
// FIXME: Isn't there a better non-SVG-specific way to do this?
if (Node* selectionBaseNode = newSelection.base().deprecatedNode())
if (RenderObject* selectionBaseRenderer = selectionBaseNode->renderer())
if (selectionBaseRenderer->isSVGText())
if (target->renderer()->containingBlock() != selectionBaseRenderer->containingBlock())
return;
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;
m_mouseDownWasInSubframe = 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();
bool caretBrowsing = m_frame->settings() && m_frame->settings()->caretBrowsingEnabled();
if (node && node->renderer() && (caretBrowsing || node->rendererIsEditable())) {
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;
}
#if OS(WIN)
void EventHandler::startPanScrolling(RenderObject* renderer)
{
if (!renderer->isBox())
return;
AutoscrollController* controller = autoscrollController();
if (!controller)
return;
controller->startPanScrolling(toRenderBox(renderer), lastKnownMousePosition());
invalidateClick();
}
#endif // OS(WIN)
AutoscrollController* EventHandler::autoscrollController() const
{
if (Page* page = m_frame->page())
return &page->autoscrollController();
return 0;
}
bool EventHandler::panScrollInProgress() const
{
return autoscrollController() && autoscrollController()->panScrollInProgress();
}
HitTestResult EventHandler::hitTestResultAtPoint(const LayoutPoint& point, HitTestRequest::HitTestRequestType hitType, const LayoutSize& padding)
{
// We always send hitTestResultAtPoint to the main frame if we have one,
// otherwise we might hit areas that are obscured by higher frames.
if (Page* page = m_frame->page()) {
Frame* mainFrame = page->mainFrame();
if (m_frame != mainFrame) {
FrameView* frameView = m_frame->view();
FrameView* mainView = mainFrame->view();
if (frameView && mainView) {
IntPoint mainFramePoint = mainView->rootViewToContents(frameView->contentsToRootView(roundedIntPoint(point)));
return mainFrame->eventHandler().hitTestResultAtPoint(mainFramePoint, hitType, padding);
}
}
}
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());
if (request.disallowsShadowContent())
result.setToNodesInDocumentTreeScope();
return result;
}
void EventHandler::stopAutoscroll()
{
if (AutoscrollController* controller = autoscrollController())
controller->stopAutoscroll();
}
Node* EventHandler::mousePressNode() const
{
return m_mousePressNode.get();
}
bool EventHandler::scrollOverflow(ScrollDirection direction, ScrollGranularity granularity, Node* startingNode)
{
Node* node = startingNode;
if (!node)
node = m_frame->document()->focusedElement();
if (!node)
node = m_mousePressNode.get();
// FIXME: Remove once scrollNode is refactored
ScrollDirection negative = ScrollUp;
if (direction == ScrollLeft)
negative = ScrollRight;
else if (direction == ScrollRight)
negative = ScrollLeft;
else if (direction == ScrollUp)
negative = ScrollDown;
else if (direction == ScrollDown)
negative = ScrollUp;
if (node) {
RenderObject* r = node->renderer();
if (r && !r->isListBox() && scrollNode(1.0f, granularity, direction, negative, node, 0)) {
setFrameWasScrolledByUser();
return true;
}
}
return false;
}
bool EventHandler::logicalScrollOverflow(ScrollLogicalDirection direction, ScrollGranularity granularity, Node* startingNode)
{
Node* node = startingNode;
if (!node)
node = m_frame->document()->focusedElement();
if (!node)
node = m_mousePressNode.get();
if (node) {
RenderObject* r = node->renderer();
if (r && !r->isListBox() && scrollNodeLogically(1.0f, granularity, direction, node, 0)) {
setFrameWasScrolledByUser();
return true;
}
}
return false;
}
bool EventHandler::scrollRecursively(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 (scrollOverflow(direction, granularity, startingNode))
return true;
Frame* frame = m_frame;
FrameView* view = frame->view();
if (view && view->scroll(direction, granularity))
return true;
frame = frame->tree().parent();
if (!frame)
return false;
return frame->eventHandler().scrollRecursively(direction, granularity, m_frame->ownerElement());
}
bool EventHandler::logicalScrollRecursively(ScrollLogicalDirection 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 (logicalScrollOverflow(direction, granularity, startingNode))
return true;
Frame* frame = m_frame;
FrameView* view = frame->view();
bool scrolled = false;
if (view && view->logicalScroll(direction, granularity))
scrolled = true;
if (scrolled)
return true;
frame = frame->tree().parent();
if (!frame)
return false;
return frame->eventHandler().logicalScrollRecursively(direction, granularity, m_frame->ownerElement());
}
IntPoint EventHandler::lastKnownMousePosition() const
{
return m_lastKnownMousePosition;
}
static Frame* subframeForTargetNode(Node* node)
{
if (!node)
return 0;
RenderObject* renderer = node->renderer();
if (!renderer || !renderer->isWidget())
return 0;
Widget* widget = toRenderWidget(renderer)->widget();
if (!widget || !widget->isFrameView())
return 0;
return &toFrameView(widget)->frame();
}
static Frame* subframeForHitTestResult(const MouseEventWithHitTestResults& hitTestResult)
{
if (!hitTestResult.isOverWidget())
return 0;
return subframeForTargetNode(hitTestResult.targetNode());
}
static bool isSubmitImage(Node* node)
{
return node && node->hasTagName(inputTag) && toHTMLInputElement(node)->isImageButton();
}
// Returns true if the node's editable block is not current focused for editing
static bool nodeIsNotBeingEdited(Node* node, Frame* frame)
{
return frame->selection().rootEditableElement() != node->rootEditableElement();
}
bool EventHandler::useHandCursor(Node* node, bool isOverLink, bool shiftKey)
{
if (!node)
return false;
bool editable = node->rendererIsEditable();
bool editableLinkEnabled = false;
// If the link is editable, then we need to check the settings to see whether or not the link should be followed
if (editable) {
ASSERT(m_frame->settings());
switch (m_frame->settings()->editableLinkBehavior()) {
default:
case EditableLinkDefaultBehavior:
case EditableLinkAlwaysLive:
editableLinkEnabled = true;
break;
case EditableLinkNeverLive:
editableLinkEnabled = false;
break;
case EditableLinkLiveWhenNotFocused:
editableLinkEnabled = nodeIsNotBeingEdited(node, m_frame) || shiftKey;
break;
case EditableLinkOnlyLiveWithShiftKey:
editableLinkEnabled = shiftKey;
break;
}
}
return ((isOverLink || isSubmitImage(node)) && (!editable || editableLinkEnabled));
}
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;
bool shiftKey;
bool ctrlKey;
bool altKey;
bool metaKey;
PlatformKeyboardEvent::getCurrentModifierState(shiftKey, ctrlKey, altKey, metaKey);
m_frame->document()->updateLayout();
HitTestRequest request(HitTestRequest::ReadOnly);
HitTestResult result(view->windowToContents(m_lastKnownMousePosition));
renderView->hitTest(request, result);
OptionalCursor optionalCursor = selectCursor(result, shiftKey);
if (optionalCursor.isCursorChange()) {
m_currentMouseCursor = optionalCursor.cursor();
view->setCursor(m_currentMouseCursor);
}
}
OptionalCursor EventHandler::selectCursor(const HitTestResult& result, bool shiftKey)
{
if (m_resizeScrollableArea && m_resizeScrollableArea->inResizeMode())
return NoCursorChange;
Page* page = m_frame->page();
if (!page)
return NoCursorChange;
#if OS(WIN)
if (panScrollInProgress())
return NoCursorChange;
#endif
Node* node = result.targetNode();
if (!node)
return selectAutoCursor(result, node, iBeamCursor(), shiftKey);
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: {
bool horizontalText = !style || style->isHorizontalWritingMode();
const Cursor& iBeam = horizontalText ? iBeamCursor() : verticalTextCursor();
return selectAutoCursor(result, node, iBeam, shiftKey);
}
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_WEBKIT_ZOOM_IN:
return zoomInCursor();
case CURSOR_WEBKIT_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 shiftKey)
{
bool editable = (node && node->rendererIsEditable());
if (useHandCursor(node, result.isOverLink(), shiftKey))
return handCursor();
bool inResizer = false;
RenderObject* renderer = node ? node->renderer() : 0;
if (renderer) {
if (RenderLayer* layer = renderer->enclosingLayer()) {
if (m_frame->view())
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(Frame* frame, const IntPoint& windowPoint)
{
FrameView* view = frame->view();
// FIXME: Is it really OK to use the wrong coordinates here when view is 0?
// Historically the code would just crash; this is clearly no worse than that.
return view ? view->windowToContents(windowPoint) : windowPoint;
}
bool EventHandler::handleMousePressEvent(const PlatformMouseEvent& mouseEvent)
{
RefPtr<FrameView> protector(m_frame->view());
bool defaultPrevented = dispatchSyntheticTouchEventIfEnabled(mouseEvent);
if (defaultPrevented)
return true;
UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture);
m_frame->tree().top()->eventHandler().m_lastMouseDownUserGestureToken = gestureIndicator.currentToken();
cancelFakeMouseMoveEvent();
if (m_eventHandlerWillResetCapturingMouseEventsNode)
m_capturingMouseEventsNode = 0;
m_mousePressed = true;
m_capturesDragging = true;
setLastKnownMousePosition(mouseEvent);
m_mouseDownTimestamp = mouseEvent.timestamp();
m_mouseDownMayStartDrag = false;
m_mouseDownMayStartSelect = false;
m_mouseDownMayStartAutoscroll = false;
if (FrameView* view = m_frame->view())
m_mouseDownPos = view->windowToContents(mouseEvent.position());
else {
invalidateClick();
return false;
}
m_mouseDownWasInSubframe = false;
HitTestRequest request(HitTestRequest::Active | HitTestRequest::ConfusingAndOftenMisusedDisallowShadowContent);
// 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();
RefPtr<Frame> subframe = subframeForHitTestResult(mev);
if (subframe && passMousePressEventToSubframe(mev, subframe.get())) {
// Start capturing future events for this frame. We only do this if we didn't clear
// the m_mousePressed flag, which may happen if an AppKit widget entered a modal event loop.
m_capturesDragging = subframe->eventHandler().capturesDragging();
if (m_mousePressed && m_capturesDragging) {
m_capturingMouseEventsNode = mev.targetNode();
m_eventHandlerWillResetCapturingMouseEventsNode = true;
}
invalidateClick();
return true;
}
#if OS(WIN)
// We store whether pan scrolling is in progress before calling stopAutoscroll()
// because it will set m_autoscrollType to NoAutoscroll on return.
bool isPanScrollInProgress = panScrollInProgress();
stopAutoscroll();
if (isPanScrollInProgress) {
// We invalidate the click when exiting pan scrolling so that we don't inadvertently navigate
// away from the current page (e.g. the click was on a hyperlink). See <rdar://problem/6095023>.
invalidateClick();
return true;
}
#endif
m_clickCount = mouseEvent.clickCount();
m_clickNode = mev.targetNode()->isTextNode() ? mev.targetNode()->parentOrShadowHostNode() : mev.targetNode();
if (FrameView* view = m_frame->view()) {
RenderLayer* layer = mev.targetNode()->renderer() ? mev.targetNode()->renderer()->enclosingLayer() : 0;
IntPoint p = view->windowToContents(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(), true, m_clickCount, mouseEvent, true);
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 | HitTestRequest::ConfusingAndOftenMisusedDisallowShadowContent);
mev = m_frame->document()->prepareMouseEvent(request, documentPoint, mouseEvent);
if (wasLastScrollBar && mev.scrollbar() != m_lastScrollbarUnderMouse.get())
m_lastScrollbarUnderMouse = 0;
}
if (swallowEvent) {
// scrollbars should get events anyway, even disabled controls might be scrollable
Scrollbar* scrollbar = mev.scrollbar();
updateLastScrollbarUnderMouse(scrollbar, true);
if (scrollbar)
passMousePressEventToScrollbar(mev, scrollbar);
} else {
if (shouldRefetchEventTarget(mev)) {
HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::ConfusingAndOftenMisusedDisallowShadowContent);
mev = m_frame->document()->prepareMouseEvent(request, documentPoint, mouseEvent);
}
FrameView* view = m_frame->view();
Scrollbar* scrollbar = view ? view->scrollbarAtPoint(mouseEvent.position()) : 0;
if (!scrollbar)
scrollbar = mev.scrollbar();
updateLastScrollbarUnderMouse(scrollbar, true);
if (scrollbar && passMousePressEventToScrollbar(mev, scrollbar))
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
{
ScrollableArea* layerScrollableArea = layer->scrollableArea();
if (!layerScrollableArea)
return 0;
if (FrameView* frameView = m_frame->view()) {
if (frameView->containsScrollableArea(layerScrollableArea))
return layerScrollableArea;
}
return 0;
}
bool EventHandler::handleMouseMoveEvent(const PlatformMouseEvent& event)
{
RefPtr<FrameView> 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();
}
if (FrameView* frameView = m_frame->view())
frameView->mouseMovedInContentArea();
hoveredNode.setToShadowHostIfInUserAgentShadowRoot();
page->chrome().mouseDidMoveOverElement(hoveredNode, event.modifierFlags());
page->chrome().setToolTip(hoveredNode);
return result;
}
void EventHandler::handleMouseLeaveEvent(const PlatformMouseEvent& event)
{
RefPtr<FrameView> protector(m_frame->view());
handleMouseMoveOrLeaveEvent(event);
}
static Cursor& syntheticTouchCursor()
{
DEFINE_STATIC_LOCAL(Cursor, c, (Image::loadPlatformResource("syntheticTouchCursor").get(), IntPoint(10, 10)));
return c;
}
bool EventHandler::handleMouseMoveOrLeaveEvent(const PlatformMouseEvent& mouseEvent, HitTestResult* hoveredNode, bool onlyUpdateScrollbars)
{
ASSERT(m_frame);
ASSERT(m_frame->view());
bool defaultPrevented = dispatchSyntheticTouchEventIfEnabled(mouseEvent);
if (defaultPrevented) {
m_frame->view()->setCursor(syntheticTouchCursor());
return true;
}
setLastKnownMousePosition(mouseEvent);
if (m_hoverTimer.isActive())
m_hoverTimer.stop();
m_cursorUpdateTimer.stop();
cancelFakeMouseMoveEvent();
if (m_svgPan) {
toSVGDocument(m_frame->document())->updatePan(m_frame->view()->windowToContents(m_lastKnownMousePosition));
return true;
}
if (m_frameSetBeingResized)
return !dispatchMouseEvent(EventTypeNames::mousemove, m_frameSetBeingResized.get(), false, 0, mouseEvent, false);
// Send events right to a scrollbar if the mouse is pressed.
if (m_lastScrollbarUnderMouse && m_mousePressed) {
m_lastScrollbarUnderMouse->mouseMoved(mouseEvent);
return true;
}
HitTestRequest::HitTestRequestType hitType = HitTestRequest::Move | HitTestRequest::ConfusingAndOftenMisusedDisallowShadowContent;
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 {
if (FrameView* view = m_frame->view())
scrollbar = view->scrollbarAtPoint(mouseEvent.position());
if (!scrollbar)
scrollbar = mev.scrollbar();
updateLastScrollbarUnderMouse(scrollbar, !m_mousePressed);
if (onlyUpdateScrollbars)
return true;
}
bool swallowEvent = false;
RefPtr<Frame> newSubframe = m_capturingMouseEventsNode.get() ? subframeForTargetNode(m_capturingMouseEventsNode.get()) : subframeForHitTestResult(mev);
// We want mouseouts to happen first, from the inside out. First send a move event to the last subframe so that it will fire mouseouts.
if (m_lastMouseMoveEventSubframe && m_lastMouseMoveEventSubframe->tree().isDescendantOf(m_frame) && m_lastMouseMoveEventSubframe != newSubframe)
passMouseMoveEventToSubframe(mev, m_lastMouseMoveEventSubframe.get());
if (newSubframe) {
// Update over/out state before passing the event to the subframe.
updateMouseEventTargetNode(mev.targetNode(), mouseEvent, true);
// Event dispatch in updateMouseEventTargetNode may have caused the subframe of the target
// node to be detached from its FrameView, in which case the event should not be passed.
if (newSubframe->view())
swallowEvent |= passMouseMoveEventToSubframe(mev, newSubframe.get(), hoveredNode);
} else {
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(), mouseEvent.shiftKey());
if (optionalCursor.isCursorChange()) {
m_currentMouseCursor = optionalCursor.cursor();
view->setCursor(m_currentMouseCursor);
}
}
}
m_lastMouseMoveEventSubframe = newSubframe;
if (swallowEvent)
return true;
swallowEvent = !dispatchMouseEvent(EventTypeNames::mousemove, mev.targetNode(), false, 0, mouseEvent, true);
if (!swallowEvent)
swallowEvent = handleMouseDraggedEvent(mev);
return swallowEvent;
}
void EventHandler::invalidateClick()
{
m_clickCount = 0;
m_clickNode = 0;
}
bool EventHandler::handleMouseReleaseEvent(const PlatformMouseEvent& mouseEvent)
{
RefPtr<FrameView> protector(m_frame->view());
m_frame->selection().setCaretBlinkingSuspended(false);
bool defaultPrevented = dispatchSyntheticTouchEventIfEnabled(mouseEvent);
if (defaultPrevented)
return true;
OwnPtr<UserGestureIndicator> gestureIndicator;
if (m_frame->tree().top()->eventHandler().m_lastMouseDownUserGestureToken)
gestureIndicator = adoptPtr(new UserGestureIndicator(m_frame->tree().top()->eventHandler().m_lastMouseDownUserGestureToken.release()));
else
gestureIndicator = adoptPtr(new UserGestureIndicator(DefinitelyProcessingUserGesture));
#if OS(WIN)
if (Page* page = m_frame->page())
page->autoscrollController().handleMouseReleaseForPanScrolling(m_frame, mouseEvent);
#endif
m_mousePressed = false;
setLastKnownMousePosition(mouseEvent);
if (m_svgPan) {
m_svgPan = false;
toSVGDocument(m_frame->document())->updatePan(m_frame->view()->windowToContents(m_lastKnownMousePosition));
return true;
}
if (m_frameSetBeingResized)
return !dispatchMouseEvent(EventTypeNames::mouseup, m_frameSetBeingResized.get(), true, m_clickCount, mouseEvent, false);
if (m_lastScrollbarUnderMouse) {
invalidateClick();
m_lastScrollbarUnderMouse->mouseUp(mouseEvent);
bool cancelable = true;
bool setUnder = false;
return !dispatchMouseEvent(EventTypeNames::mouseup, m_lastNodeUnderMouse.get(), cancelable, m_clickCount, mouseEvent, setUnder);
}
HitTestRequest::HitTestRequestType hitType = HitTestRequest::Release | HitTestRequest::ConfusingAndOftenMisusedDisallowShadowContent;
if (m_shouldKeepActiveForMinInterval)
hitType |= HitTestRequest::ReadOnly;
HitTestRequest request(hitType);
MouseEventWithHitTestResults mev = prepareMouseEvent(request, mouseEvent);
Frame* subframe = m_capturingMouseEventsNode.get() ? subframeForTargetNode(m_capturingMouseEventsNode.get()) : subframeForHitTestResult(mev);
if (m_eventHandlerWillResetCapturingMouseEventsNode)
m_capturingMouseEventsNode = 0;
if (subframe && passMouseReleaseEventToSubframe(mev, subframe))
return true;
if (m_shouldKeepActiveForMinInterval)
m_lastDeferredTapElement = mev.hitTestResult().innerElement();
bool swallowMouseUpEvent = !dispatchMouseEvent(EventTypeNames::mouseup, mev.targetNode(), true, m_clickCount, mouseEvent, false);
bool contextMenuEvent = mouseEvent.button() == RightButton;
#if OS(MACOSX)
// FIXME: The Mac port achieves the same behavior by checking whether the context menu is currently open in WebPage::mouseEvent(). Consider merging the implementations.
if (mouseEvent.button() == LeftButton && mouseEvent.modifiers() & PlatformEvent::CtrlKey)
contextMenuEvent = true;
#endif
bool swallowClickEvent = false;
if (m_clickCount > 0 && !contextMenuEvent && mev.targetNode() && m_clickNode) {
if (Node* clickTargetNode = mev.targetNode()->commonAncestorOverShadowBoundary(*m_clickNode))
swallowClickEvent = !dispatchMouseEvent(EventTypeNames::click, clickTargetNode, true, 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;
Frame* 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;
}
bool EventHandler::dispatchDragEvent(const AtomicString& eventType, Node* dragTarget, const PlatformMouseEvent& event, Clipboard* clipboard)
{
FrameView* view = m_frame->view();
// FIXME: We might want to dispatch a dragleave even if the view is gone.
if (!view)
return false;
view->resetDeferredRepaintDelay();
RefPtr<MouseEvent> me = MouseEvent::create(eventType,
true, true, m_frame->document()->domWindow(),
0, event.globalPosition().x(), event.globalPosition().y(), event.position().x(), event.position().y(),
event.movementDelta().x(), event.movementDelta().y(),
event.ctrlKey(), event.altKey(), event.shiftKey(), event.metaKey(),
0, 0, clipboard);
dragTarget->dispatchEvent(me.get(), IGNORE_EXCEPTION);
return me->defaultPrevented();
}
static bool targetIsFrame(Node* target, Frame*& frame)
{
if (!target)
return false;
if (!target->hasTagName(frameTag) && !target->hasTagName(iframeTag))
return false;
frame = toHTMLFrameElementBase(target)->contentFrame();
return true;
}
static bool findDropZone(Node* target, Clipboard* clipboard)
{
Element* element = target->isElementNode() ? toElement(target) : target->parentElement();
for (; element; element = element->parentElement()) {
bool matched = false;
AtomicString dropZoneStr = element->fastGetAttribute(webkitdropzoneAttr);
if (dropZoneStr.isEmpty())
continue;
dropZoneStr = dropZoneStr.lower();
SpaceSplitString keywords(dropZoneStr, false);
if (keywords.isNull())
continue;
DragOperation dragOperation = DragOperationNone;
for (unsigned int i = 0; i < keywords.size(); i++) {
DragOperation op = convertDropZoneOperationToDragOperation(keywords[i]);
if (op != DragOperationNone) {
if (dragOperation == DragOperationNone)
dragOperation = op;
} else
matched = matched || clipboard->hasDropZoneType(keywords[i].string());
if (matched && dragOperation != DragOperationNone)
break;
}
if (matched) {
clipboard->setDropEffect(convertDragOperationToDropZoneOperation(dragOperation));
return true;
}
}
return false;
}
bool EventHandler::updateDragAndDrop(const PlatformMouseEvent& event, Clipboard* clipboard)
{
bool accept = false;
if (!m_frame->view())
return false;
HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::ConfusingAndOftenMisusedDisallowShadowContent);
MouseEventWithHitTestResults mev = prepareMouseEvent(request, event);
// Drag events should never go to text nodes (following IE, and proper mouseover/out dispatch)
RefPtr<Node> newTarget = mev.targetNode();
if (newTarget && newTarget->isTextNode())
newTarget = EventPath::parent(newTarget.get());
if (AutoscrollController* controller = autoscrollController())
controller->updateDragAndDrop(newTarget.get(), event.position(), event.timestamp());
if (m_dragTarget != newTarget) {
// FIXME: this ordering was explicitly chosen to match WinIE. However,
// it is sometimes incorrect when dragging within subframes, as seen with
// LayoutTests/fast/events/drag-in-frames.html.
//
// Moreover, this ordering conforms to section 7.9.4 of the HTML 5 spec. <http://dev.w3.org/html5/spec/Overview.html#drag-and-drop-processing-model>.
Frame* targetFrame;
if (targetIsFrame(newTarget.get(), targetFrame)) {
if (targetFrame)
accept = targetFrame->eventHandler().updateDragAndDrop(event, clipboard);
} else if (newTarget) {
// As per section 7.9.4 of the HTML 5 spec., we must always fire a drag event before firing a dragenter, dragleave, or dragover event.
if (dragState().m_dragSrc) {
// for now we don't care if event handler cancels default behavior, since there is none
dispatchDragSrcEvent(EventTypeNames::drag, event);
}
accept = dispatchDragEvent(EventTypeNames::dragenter, newTarget.get(), event, clipboard);
if (!accept)
accept = findDropZone(newTarget.get(), clipboard);
}
if (targetIsFrame(m_dragTarget.get(), targetFrame)) {
if (targetFrame)
accept = targetFrame->eventHandler().updateDragAndDrop(event, clipboard);
} else if (m_dragTarget)
dispatchDragEvent(EventTypeNames::dragleave, m_dragTarget.get(), event, clipboard);
if (newTarget) {
// We do not explicitly call dispatchDragEvent here because it could ultimately result in the appearance that
// two dragover events fired. So, we mark that we should only fire a dragover event on the next call to this function.
m_shouldOnlyFireDragOverEvent = true;
}
} else {
Frame* targetFrame;
if (targetIsFrame(newTarget.get(), targetFrame)) {
if (targetFrame)
accept = targetFrame->eventHandler().updateDragAndDrop(event, clipboard);
} else if (newTarget) {
// Note, when dealing with sub-frames, we may need to fire only a dragover event as a drag event may have been fired earlier.
if (!m_shouldOnlyFireDragOverEvent && dragState().m_dragSrc) {
// for now we don't care if event handler cancels default behavior, since there is none
dispatchDragSrcEvent(EventTypeNames::drag, event);
}
accept = dispatchDragEvent(EventTypeNames::dragover, newTarget.get(), event, clipboard);
if (!accept)
accept = findDropZone(newTarget.get(), clipboard);
m_shouldOnlyFireDragOverEvent = false;
}
}
m_dragTarget = newTarget;
return accept;
}
void EventHandler::cancelDragAndDrop(const PlatformMouseEvent& event, Clipboard* clipboard)
{
Frame* targetFrame;
if (targetIsFrame(m_dragTarget.get(), targetFrame)) {
if (targetFrame)
targetFrame->eventHandler().cancelDragAndDrop(event, clipboard);
} else if (m_dragTarget.get()) {
if (dragState().m_dragSrc)
dispatchDragSrcEvent(EventTypeNames::drag, event);
dispatchDragEvent(EventTypeNames::dragleave, m_dragTarget.get(), event, clipboard);
}
clearDragState();
}
bool EventHandler::performDragAndDrop(const PlatformMouseEvent& event, Clipboard* clipboard)
{
Frame* targetFrame;
bool preventedDefault = false;
if (targetIsFrame(m_dragTarget.get(), targetFrame)) {
if (targetFrame)
preventedDefault = targetFrame->eventHandler().performDragAndDrop(event, clipboard);
} else if (m_dragTarget.get())
preventedDefault = dispatchDragEvent(EventTypeNames::drop, m_dragTarget.get(), event, clipboard);
clearDragState();
return preventedDefault;
}
void EventHandler::clearDragState()
{
stopAutoscroll();
m_dragTarget = 0;
m_capturingMouseEventsNode = 0;
m_shouldOnlyFireDragOverEvent = false;
}
void EventHandler::setCapturingMouseEventsNode(PassRefPtr<Node> 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);
}
static inline SVGElementInstance* instanceAssociatedWithShadowTreeElement(Node* referenceNode)
{
if (!referenceNode || !referenceNode->isSVGElement())
return 0;
ShadowRoot* shadowRoot = referenceNode->containingShadowRoot();
if (!shadowRoot)
return 0;
Element* shadowTreeParentElement = shadowRoot->host();
if (!shadowTreeParentElement || !shadowTreeParentElement->hasTagName(useTag))
return 0;
return toSVGUseElement(shadowTreeParentElement)->instanceForShadowTreeElement(referenceNode);
}
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 = EventPath::parent(result);
}
m_nodeUnderMouse = result;
m_instanceUnderMouse = instanceAssociatedWithShadowTreeElement(result);
// <use> shadow tree elements may have been recloned, update node under mouse in any case
if (m_lastInstanceUnderMouse) {
SVGElement* lastCorrespondingElement = m_lastInstanceUnderMouse->correspondingElement();
SVGElement* lastCorrespondingUseElement = m_lastInstanceUnderMouse->correspondingUseElement();
if (lastCorrespondingElement && lastCorrespondingUseElement) {
HashSet<SVGElementInstance*> instances = lastCorrespondingElement->instancesForElement();
// Locate the recloned shadow tree element for our corresponding instance
HashSet<SVGElementInstance*>::iterator end = instances.end();
for (HashSet<SVGElementInstance*>::iterator it = instances.begin(); it != end; ++it) {
SVGElementInstance* instance = (*it);
ASSERT(instance->correspondingElement() == lastCorrespondingElement);
if (instance == m_lastInstanceUnderMouse)
continue;
if (instance->correspondingUseElement() != lastCorrespondingUseElement)
continue;
SVGElement* shadowTreeElement = instance->shadowTreeElement();
if (!shadowTreeElement->inDocument() || m_lastNodeUnderMouse == shadowTreeElement)
continue;
m_lastNodeUnderMouse = shadowTreeElement;
m_lastInstanceUnderMouse = instance;
break;
}
}
}
// 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 (m_lastNodeUnderMouse && (!m_nodeUnderMouse || m_nodeUnderMouse->document() != m_frame->document())) {
// The mouse has moved between frames.
if (Frame* frame = m_lastNodeUnderMouse->document().frame()) {
if (FrameView* frameView = frame->view())
frameView->mouseExitedContentArea();
}
} else if (page && (layerForLastNode && (!layerForNodeUnderMouse || layerForNodeUnderMouse != layerForLastNode))) {
// The mouse has moved between layers.
if (ScrollableArea* scrollableAreaForLastNode = associatedScrollableArea(layerForLastNode))
scrollableAreaForLastNode->mouseExitedContentArea();
}
if (m_nodeUnderMouse && (!m_lastNodeUnderMouse || m_lastNodeUnderMouse->document() != m_frame->document())) {
// The mouse has moved between frames.
if (Frame* frame = m_nodeUnderMouse->document().frame()) {
if (FrameView* frameView = frame->view())
frameView->mouseEnteredContentArea();
}
} else 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 = 0;
m_lastScrollbarUnderMouse = 0;
m_lastInstanceUnderMouse = 0;
}
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;
m_lastInstanceUnderMouse = instanceAssociatedWithShadowTreeElement(m_nodeUnderMouse.get());
}
}
bool EventHandler::dispatchMouseEvent(const AtomicString& eventType, Node* targetNode, bool /*cancelable*/, int clickCount, const PlatformMouseEvent& mouseEvent, bool setUnder)
{
if (FrameView* view = m_frame->view())
view->resetDeferredRepaintDelay();
updateMouseEventTargetNode(targetNode, mouseEvent, setUnder);
bool swallowEvent = false;
if (m_nodeUnderMouse)
swallowEvent = !(m_nodeUnderMouse->dispatchMouseEvent(mouseEvent, eventType, clickCount));
if (swallowEvent || eventType != EventTypeNames::mousedown)
return !swallowEvent;
// If clicking on a frame scrollbar, do not mess up with content focus.
if (FrameView* view = m_frame->view()) {
if (view->scrollbarAtPoint(mouseEvent.position()))
return true;
}
// 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 !swallowEvent;
if (element->isMouseFocusable())
break;
}
ASSERT(!element || element->isMouseFocusable());
// To fix <rdar://problem/4895428> 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 true;
// Only change the focus when clicking scrollbars if it can transfered to a
// mouse focusable node.
if (!element && isInsideScrollbar(mouseEvent.position()))
return false;
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, FocusDirectionMouse))
swallowEvent = 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))
swallowEvent = true;
}
}
return !swallowEvent;
}
bool EventHandler::isInsideScrollbar(const IntPoint& windowPoint) const
{
if (RenderView* renderView = m_frame->contentRenderer()) {
HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::ConfusingAndOftenMisusedDisallowShadowContent);
HitTestResult result(windowPoint);
renderView->hitTest(request, result);
return result.scrollbar();
}
return false;
}
bool EventHandler::shouldTurnVerticalTicksIntoHorizontal(const HitTestResult& result, const PlatformWheelEvent& event) const
{
#if OS(ANDROID) || OS(MACOSX) || OS(WIN)
return false;
#else
// GTK+ must scroll horizontally if the mouse pointer is on top of the
// horizontal scrollbar while scrolling with the wheel.
// This code comes from gtk/EventHandlerGtk.cpp.
return !event.hasPreciseScrollingDeltas() && result.scrollbar() && result.scrollbar()->orientation() == HorizontalScrollbar;
#endif
}
bool EventHandler::handleWheelEvent(const PlatformWheelEvent& e)
{
#define RETURN_WHEEL_EVENT_HANDLED() \
{ \
setFrameWasScrolledByUser(); \
return true; \
}
Document* doc = m_frame->document();
if (!doc->renderer())
return false;
RefPtr<FrameView> protector(m_frame->view());
FrameView* view = m_frame->view();
if (!view)
return false;
if (handleWheelEventAsEmulatedGesture(e))
return true;
LayoutPoint vPoint = view->windowToContents(e.position());
HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::ConfusingAndOftenMisusedDisallowShadowContent);
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 = EventPath::parent(node);
bool isOverWidget;
if (e.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 = 0;
if (m_previousWheelScrolledNode)
m_previousWheelScrolledNode = 0;
isOverWidget = result.isOverWidget();
}
// FIXME: It should not be necessary to do this mutation here.
// Instead, the handlers should know convert vertical scrolls
// appropriately.
PlatformWheelEvent event = e;
if (m_baseEventType == PlatformEvent::NoType && shouldTurnVerticalTicksIntoHorizontal(result, e))
event = event.copyTurningVerticalTicksIntoHorizontalTicks();
if (node) {
// Figure out which view to send the event to.
RenderObject* target = node->renderer();
if (isOverWidget && target && target->isWidget()) {
Widget* widget = toRenderWidget(target)->widget();
if (widget && passWheelEventToWidget(e, widget))
RETURN_WHEEL_EVENT_HANDLED();
}
if (node && !node->dispatchWheelEvent(event))
RETURN_WHEEL_EVENT_HANDLED();
}
// 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_WHEEL_EVENT_HANDLED();
#undef RETURN_WHEEL_EVENT_HANDLED
}
void EventHandler::defaultWheelEventHandler(Node* startNode, WheelEvent* wheelEvent)
{
if (!startNode || !wheelEvent)
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 (scrollNode(wheelEvent->deltaX(), granularity, ScrollRight, ScrollLeft, startNode, &stopNode, roundedIntPoint(wheelEvent->absoluteLocation())))
wheelEvent->setDefaultHandled();
if (scrollNode(wheelEvent->deltaY(), granularity, ScrollDown, ScrollUp, startNode, &stopNode, 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;
if (ScrollAnimator* scrollAnimator = view->existingScrollAnimator())
scrollAnimator->cancelAnimations();
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->scrollAnimator();
if (animator)
animator->cancelAnimations();
}
return false;
}
bool EventHandler::handleGestureEvent(const PlatformGestureEvent& gestureEvent)
{
IntPoint adjustedPoint = gestureEvent.position();
RefPtr<Frame> subframe = 0;
switch (gestureEvent.type()) {
case PlatformEvent::GestureScrollBegin:
case PlatformEvent::GestureScrollUpdate:
case PlatformEvent::GestureScrollUpdateWithoutPropagation:
case PlatformEvent::GestureScrollEnd:
// Handle directly in main frame
break;
case PlatformEvent::GestureTap:
case PlatformEvent::GestureTapUnconfirmed:
case PlatformEvent::GestureTapDown:
case PlatformEvent::GestureShowPress:
case PlatformEvent::GestureTapDownCancel:
case PlatformEvent::GestureTwoFingerTap:
case PlatformEvent::GestureLongPress:
case PlatformEvent::GestureLongTap:
case PlatformEvent::GesturePinchBegin:
case PlatformEvent::GesturePinchEnd:
case PlatformEvent::GesturePinchUpdate:
adjustGesturePosition(gestureEvent, adjustedPoint);
subframe = getSubFrameForGestureEvent(adjustedPoint, gestureEvent);
if (subframe)
return subframe->eventHandler().handleGestureEvent(gestureEvent);
break;
default:
ASSERT_NOT_REACHED();
}
Node* eventTarget = 0;
Scrollbar* scrollbar = 0;
if (gestureEvent.type() == PlatformEvent::GestureScrollEnd
|| gestureEvent.type() == PlatformEvent::GestureScrollUpdate
|| gestureEvent.type() == PlatformEvent::GestureScrollUpdateWithoutPropagation) {
scrollbar = m_scrollbarHandlingScrollGesture.get();
eventTarget = m_scrollGestureHandlingNode.get();
}
HitTestRequest::HitTestRequestType hitType = HitTestRequest::TouchEvent;
if (gestureEvent.type() == PlatformEvent::GestureShowPress
|| gestureEvent.type() == PlatformEvent::GestureTapUnconfirmed) {
hitType |= HitTestRequest::Active;
} else if (gestureEvent.type() == PlatformEvent::GestureTapDownCancel) {
hitType |= HitTestRequest::Release;
// A TapDownCancel received when no element is active shouldn't really be changing hover state.
if (!m_frame->document()->activeElement())
hitType |= HitTestRequest::ReadOnly;
} else if (gestureEvent.type() == PlatformEvent::GestureTap) {
// The mouseup event synthesized for this gesture will clear the active state of the
// targeted node, so performing a ReadOnly hit test here is fine.
hitType |= HitTestRequest::ReadOnly;
}
else
hitType |= HitTestRequest::Active | HitTestRequest::ReadOnly;
if ((!scrollbar && !eventTarget) || !(hitType & HitTestRequest::ReadOnly)) {
IntPoint hitTestPoint = m_frame->view()->windowToContents(adjustedPoint);
HitTestResult result = hitTestResultAtPoint(hitTestPoint, hitType | HitTestRequest::AllowFrameScrollbars);
eventTarget = result.targetNode();
if (!scrollbar) {
FrameView* view = m_frame->view();
scrollbar = view ? view->scrollbarAtPoint(gestureEvent.position()) : 0;
}
if (!scrollbar)
scrollbar = result.scrollbar();
}
if (scrollbar) {
bool eventSwallowed = scrollbar->gestureEvent(gestureEvent);
if (gestureEvent.type() == PlatformEvent::GestureScrollBegin && eventSwallowed)
m_scrollbarHandlingScrollGesture = scrollbar;
else if (gestureEvent.type() == PlatformEvent::GestureScrollEnd || !eventSwallowed)
m_scrollbarHandlingScrollGesture = 0;
if (eventSwallowed)
return true;
}
if (eventTarget) {
bool eventSwallowed = false;
if (handleScrollGestureOnResizer(eventTarget, gestureEvent))
eventSwallowed = true;
else
eventSwallowed = eventTarget->dispatchGestureEvent(gestureEvent);
if (gestureEvent.type() == PlatformEvent::GestureScrollBegin || gestureEvent.type() == PlatformEvent::GestureScrollEnd) {
if (eventSwallowed)
m_scrollGestureHandlingNode = eventTarget;
}
if (eventSwallowed)
return true;
}
// FIXME: A more general scroll system (https://bugs.webkit.org/show_bug.cgi?id=80596) will
// eliminate the need for this.
TemporaryChange<PlatformEvent::Type> baseEventType(m_baseEventType, gestureEvent.type());
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::GestureTap:
return handleGestureTap(gestureEvent, adjustedPoint);
case PlatformEvent::GestureShowPress:
return handleGestureShowPress();
case PlatformEvent::GestureLongPress:
return handleGestureLongPress(gestureEvent, adjustedPoint);
case PlatformEvent::GestureLongTap:
return handleGestureLongTap(gestureEvent, adjustedPoint);
case PlatformEvent::GestureTwoFingerTap:
return handleGestureTwoFingerTap(gestureEvent, adjustedPoint);
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::handleGestureTap(const PlatformGestureEvent& gestureEvent, const IntPoint& adjustedPoint)
{
// FIXME: Refactor this code to not hit test multiple times. We use the adjusted position to ensure that the correct node is targeted by the later redundant hit tests.
PlatformMouseEvent fakeMouseMove(adjustedPoint, gestureEvent.globalPosition(),
NoButton, PlatformEvent::MouseMoved, /* clickCount */ 0,
gestureEvent.shiftKey(), gestureEvent.ctrlKey(), gestureEvent.altKey(), gestureEvent.metaKey(), gestureEvent.timestamp());
handleMouseMoveEvent(fakeMouseMove);
bool defaultPrevented = false;
PlatformMouseEvent fakeMouseDown(adjustedPoint, gestureEvent.globalPosition(),
LeftButton, PlatformEvent::MousePressed, gestureEvent.tapCount(),
gestureEvent.shiftKey(), gestureEvent.ctrlKey(), gestureEvent.altKey(), gestureEvent.metaKey(), gestureEvent.timestamp());
defaultPrevented |= handleMousePressEvent(fakeMouseDown);
PlatformMouseEvent fakeMouseUp(adjustedPoint, gestureEvent.globalPosition(),
LeftButton, PlatformEvent::MouseReleased, gestureEvent.tapCount(),
gestureEvent.shiftKey(), gestureEvent.ctrlKey(), gestureEvent.altKey(), gestureEvent.metaKey(), gestureEvent.timestamp());
// 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 one frame.
double activeInterval = WTF::currentTime() - m_lastShowPressTimestamp;
m_shouldKeepActiveForMinInterval = m_lastShowPressTimestamp && activeInterval < minimumActiveInterval;
defaultPrevented |= handleMouseReleaseEvent(fakeMouseUp);
if (m_shouldKeepActiveForMinInterval)
m_activeIntervalTimer.startOneShot(minimumActiveInterval - activeInterval);
m_shouldKeepActiveForMinInterval = false;
return defaultPrevented;
}
bool EventHandler::handleGestureLongPress(const PlatformGestureEvent& gestureEvent, const IntPoint& adjustedPoint)
{
m_longTapShouldInvokeContextMenu = false;
if (m_frame->settings() && m_frame->settings()->touchDragDropEnabled() && m_frame->view()) {
PlatformMouseEvent mouseDownEvent(adjustedPoint, gestureEvent.globalPosition(), LeftButton, PlatformEvent::MousePressed, 1,
gestureEvent.shiftKey(), gestureEvent.ctrlKey(), gestureEvent.altKey(), gestureEvent.metaKey(), WTF::currentTime());
m_mouseDown = mouseDownEvent;
PlatformMouseEvent mouseDragEvent(adjustedPoint, gestureEvent.globalPosition(), LeftButton, PlatformEvent::MouseMoved, 1,
gestureEvent.shiftKey(), gestureEvent.ctrlKey(), gestureEvent.altKey(), gestureEvent.metaKey(), WTF::currentTime());
HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::ConfusingAndOftenMisusedDisallowShadowContent);
MouseEventWithHitTestResults mev = prepareMouseEvent(request, mouseDragEvent);
m_didStartDrag = false;
m_mouseDownMayStartDrag = true;
dragState().m_dragSrc = 0;
m_mouseDownPos = m_frame->view()->windowToContents(mouseDragEvent.position());
RefPtr<FrameView> protector(m_frame->view());
handleDrag(mev, DontCheckDragHysteresis);
if (m_didStartDrag) {
m_longTapShouldInvokeContextMenu = true;
return true;
}
}
#if OS(ANDROID)
bool shouldLongPressSelectWord = true;
#else
bool shouldLongPressSelectWord = m_frame->settings() && m_frame->settings()->touchEditingEnabled();
#endif
if (shouldLongPressSelectWord) {
IntPoint hitTestPoint = m_frame->view()->windowToContents(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 sendContextMenuEventForGesture(gestureEvent);
}
bool EventHandler::handleGestureLongTap(const PlatformGestureEvent& gestureEvent, const IntPoint& adjustedPoint)
{
#if !OS(ANDROID)
if (m_longTapShouldInvokeContextMenu) {
m_longTapShouldInvokeContextMenu = false;
return sendContextMenuEventForGesture(gestureEvent);
}
#endif
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 = m_frame->view()->windowToContents(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::handleGestureTwoFingerTap(const PlatformGestureEvent& gestureEvent, const IntPoint& adjustedPoint)
{
return sendContextMenuEventForGesture(gestureEvent);
}
bool EventHandler::passGestureEventToWidget(const PlatformGestureEvent& gestureEvent, Widget* widget)
{
if (!widget)
return false;
if (!widget->isFrameView())
return false;
return toFrameView(widget)->frame().eventHandler().handleGestureEvent(gestureEvent);
}
bool EventHandler::passGestureEventToWidgetIfPossible(const PlatformGestureEvent& gestureEvent, RenderObject* renderer)
{
if (m_lastHitTestResultOverWidget && renderer && renderer->isWidget()) {
Widget* widget = toRenderWidget(renderer)->widget();
return widget && passGestureEventToWidget(gestureEvent, widget);
}
return false;
}
bool EventHandler::handleGestureScrollEnd(const PlatformGestureEvent& gestureEvent) {
RefPtr<Node> node = m_scrollGestureHandlingNode;
clearGestureScrollNodes();
if (node) {
ASSERT(node->refCount() > 0);
passGestureEventToWidgetIfPossible(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;
LayoutPoint viewPoint = view->windowToContents(gestureEvent.position());
HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::ConfusingAndOftenMisusedDisallowShadowContent);
HitTestResult result(viewPoint);
document->renderView()->hitTest(request, result);
m_lastHitTestResultOverWidget = result.isOverWidget();
m_scrollGestureHandlingNode = result.innerNode();
m_previousGestureScrolledNode = 0;
// 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;
passGestureEventToWidgetIfPossible(gestureEvent, m_scrollGestureHandlingNode->renderer());
return true;
}
bool EventHandler::handleGestureScrollUpdate(const PlatformGestureEvent& gestureEvent)
{
FloatSize delta(gestureEvent.deltaX(), gestureEvent.deltaY());
if (delta.isZero())
return false;
const float scaleFactor = m_frame->pageZoomFactor();
delta.scale(1 / scaleFactor, 1 / scaleFactor);
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 (passGestureEventToWidgetIfPossible(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 = scrollNode(delta.width(), granularity, ScrollLeft, ScrollRight, node, &stopNode);
bool verticalScroll = scrollNode(delta.height(), granularity, ScrollUp, ScrollDown, node, &stopNode);
if (scrollShouldNotPropagate)
m_previousGestureScrolledNode = stopNode;
if (horizontalScroll || verticalScroll) {
setFrameWasScrolledByUser();
return true;
}
// Otherwise try to scroll the view.
return sendScrollEventToView(gestureEvent, delta);
}
bool EventHandler::sendScrollEventToView(const PlatformGestureEvent& gestureEvent, const FloatSize& scaledDelta)
{
FrameView* view = m_frame->view();
if (!view)
return false;
const float tickDivisor = static_cast<float>(WheelEvent::TickMultiplier);
IntPoint point(gestureEvent.position().x(), gestureEvent.position().y());
IntPoint globalPoint(gestureEvent.globalPosition().x(), gestureEvent.globalPosition().y());
PlatformWheelEvent syntheticWheelEvent(point, globalPoint,
scaledDelta.width(), scaledDelta.height(),
scaledDelta.width() / tickDivisor, scaledDelta.height() / tickDivisor,
ScrollByPixelWheelEvent,
gestureEvent.shiftKey(), gestureEvent.ctrlKey(), gestureEvent.altKey(), gestureEvent.metaKey());
syntheticWheelEvent.setHasPreciseScrollingDeltas(true);
bool scrolledFrame = view->wheelEvent(syntheticWheelEvent);
if (scrolledFrame)
setFrameWasScrolledByUser();
return scrolledFrame;
}
Frame* EventHandler::getSubFrameForGestureEvent(const IntPoint& touchAdjustedPoint, const PlatformGestureEvent& gestureEvent)
{
PlatformMouseEvent mouseDown(touchAdjustedPoint, gestureEvent.globalPosition(), LeftButton, PlatformEvent::MousePressed, 1,
gestureEvent.shiftKey(), gestureEvent.ctrlKey(), gestureEvent.altKey(), gestureEvent.metaKey(), gestureEvent.timestamp());
HitTestRequest request(HitTestRequest::ReadOnly);
MouseEventWithHitTestResults mev = prepareMouseEvent(request, mouseDown);
return subframeForHitTestResult(mev);
}
void EventHandler::clearGestureScrollNodes()
{
m_scrollGestureHandlingNode = 0;
m_previousGestureScrolledNode = 0;
}
bool EventHandler::isScrollbarHandlingGestures() const
{
return m_scrollbarHandlingScrollGesture.get();
}
bool EventHandler::shouldApplyTouchAdjustment(const PlatformGestureEvent& event) const
{
if (m_frame->settings() && !m_frame->settings()->touchAdjustmentEnabled())
return false;
return !event.area().isEmpty();
}
bool EventHandler::bestClickableNodeForTouchPoint(const IntPoint& touchCenter, const IntSize& touchRadius, IntPoint& targetPoint, Node*& targetNode)
{
IntPoint hitTestPoint = m_frame->view()->windowToContents(touchCenter);
HitTestResult result = hitTestResultAtPoint(hitTestPoint, HitTestRequest::ReadOnly | HitTestRequest::Active, touchRadius);
// If the touch is over a scrollbar, don't adjust the touch point since touch adjustment only takes into account
// DOM nodes so a touch over a scrollbar will be adjusted towards nearby nodes. This leads to things like textarea
// scrollbars being untouchable.
if (result.scrollbar())
return false;
IntRect touchRect(touchCenter - touchRadius, touchRadius + touchRadius);
Vector<RefPtr<Node>, 11> nodes;
copyToVector(result.rectBasedTestResult(), nodes);
// FIXME: Should be able to handle targetNode being a shadow DOM node to avoid performing uncessary hit tests
// in the case where further processing on the node is required. Returning the shadow ancestor prevents a
// regression in touchadjustment/html-label.html. Some refinement is required to testing/internals to
// handle targetNode being a shadow DOM node.
// FIXME: the explicit Vector conversion copies into a temporary and is
// wasteful.
bool success = findBestClickableCandidate(targetNode, targetPoint, touchCenter, touchRect, Vector<RefPtr<Node> > (nodes));
if (success && targetNode)
targetNode = targetNode->deprecatedShadowAncestorNode();
return success;
}
bool EventHandler::bestContextMenuNodeForTouchPoint(const IntPoint& touchCenter, const IntSize& touchRadius, IntPoint& targetPoint, Node*& targetNode)
{
IntPoint hitTestPoint = m_frame->view()->windowToContents(touchCenter);
HitTestResult result = hitTestResultAtPoint(hitTestPoint, HitTestRequest::ReadOnly | HitTestRequest::Active, touchRadius);
IntRect touchRect(touchCenter - touchRadius, touchRadius + touchRadius);
Vector<RefPtr<Node>, 11> nodes;
copyToVector(result.rectBasedTestResult(), nodes);
// FIXME: the explicit Vector conversion copies into a temporary and is
// wasteful.
return findBestContextMenuCandidate(targetNode, targetPoint, touchCenter, touchRect, Vector<RefPtr<Node> >(nodes));
}
bool EventHandler::bestZoomableAreaForTouchPoint(const IntPoint& touchCenter, const IntSize& touchRadius, IntRect& targetArea, Node*& targetNode)
{
IntPoint hitTestPoint = m_frame->view()->windowToContents(touchCenter);
HitTestResult result = hitTestResultAtPoint(hitTestPoint, HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::ConfusingAndOftenMisusedDisallowShadowContent, touchRadius);
IntRect touchRect(touchCenter - touchRadius, touchRadius + touchRadius);
Vector<RefPtr<Node>, 11> nodes;
copyToVector(result.rectBasedTestResult(), nodes);
// FIXME: the explicit Vector conversion copies into a temporary and is
// wasteful.
return findBestZoomableArea(targetNode, targetArea, touchCenter, touchRect, Vector<RefPtr<Node> >(nodes));
}
bool EventHandler::adjustGesturePosition(const PlatformGestureEvent& gestureEvent, IntPoint& adjustedPoint)
{
if (!shouldApplyTouchAdjustment(gestureEvent))
return false;
Node* targetNode = 0;
switch (gestureEvent.type()) {
case PlatformEvent::GestureTap:
case PlatformEvent::GestureTapUnconfirmed:
case PlatformEvent::GestureTapDown:
case PlatformEvent::GestureShowPress:
bestClickableNodeForTouchPoint(gestureEvent.position(), IntSize(gestureEvent.area().width() / 2, gestureEvent.area().height() / 2), adjustedPoint, targetNode);
break;
case PlatformEvent::GestureLongPress:
case PlatformEvent::GestureLongTap:
case PlatformEvent::GestureTwoFingerTap:
bestContextMenuNodeForTouchPoint(gestureEvent.position(), IntSize(gestureEvent.area().width() / 2, gestureEvent.area().height() / 2), adjustedPoint, targetNode);
break;
default:
// FIXME: Implement handling for other types as needed.
ASSERT_NOT_REACHED();
}
return targetNode;
}
bool EventHandler::sendContextMenuEvent(const PlatformMouseEvent& event)
{
Document* doc = m_frame->document();
FrameView* v = m_frame->view();
if (!v)
return false;
// Clear mouse press state to avoid initiating a drag while context menu is up.
m_mousePressed = false;
bool swallowEvent;
LayoutPoint viewportPos = v->windowToContents(event.position());
HitTestRequest request(HitTestRequest::Active | HitTestRequest::ConfusingAndOftenMisusedDisallowShadowContent);
MouseEventWithHitTestResults mev = doc->prepareMouseEvent(request, viewportPos, event);
if (!m_frame->selection().contains(viewportPos)
&& !mev.scrollbar()
// FIXME: In the editable case, word selection sometimes selects content that isn't underneath the mouse.
// If the selection is non-editable, we do word selection to make it easier to use the contextual menu items
// available for text selections. But only if we're above text.
&& (m_frame->selection().isContentEditable() || (mev.targetNode() && mev.targetNode()->isTextNode()))) {
m_mouseDownMayStartSelect = true; // context menu events are always allowed to perform a selection
if (mev.hitTestResult().isMisspelled())
selectClosestMisspellingFromMouseEvent(mev);
else if (m_frame->editor().behavior().shouldSelectOnContextualMenuClick())
selectClosestWordOrLinkFromMouseEvent(mev);
}
swallowEvent = !dispatchMouseEvent(EventTypeNames::contextmenu, mev.targetNode(), true, 0, event, false);
return swallowEvent;
}
bool EventHandler::sendContextMenuEventForKey()
{
FrameView* view = m_frame->view();
if (!view)
return false;
Document* doc = m_frame->document();
if (!doc)
return false;
// Clear mouse press state to avoid initiating a drag while context menu is up.
m_mousePressed = false;
static const int kContextMenuMargin = 1;
#if OS(WIN)
int rightAligned = ::GetSystemMetrics(SM_MENUDROPALIGNMENT);
#else
int rightAligned = 0;
#endif
IntPoint location;
Element* focusedElement = doc->focusedElement();
FrameSelection& selection = m_frame->selection();
Position start = selection.selection().start();
if (start.deprecatedNode() && (selection.rootEditableElement() || selection.isRange())) {
RefPtr<Range> selectionRange = selection.toNormalizedRange();
IntRect firstRect = m_frame->editor().firstRectForRange(selectionRange.get());
int x = rightAligned ? firstRect.maxX() : firstRect.x();
// In a multiline edit, firstRect.maxY() would endup on the next line, so -1.
int y = firstRect.maxY() ? firstRect.maxY() - 1 : 0;
location = IntPoint(x, y);
} else if (focusedElement) {
RenderBoxModelObject* box = focusedElement->renderBoxModelObject();
if (!box)
return false;
IntRect clippedRect = box->pixelSnappedAbsoluteClippedOverflowRect();
location = IntPoint(clippedRect.x(), clippedRect.maxY() - 1);
} else {
location = IntPoint(
rightAligned ? view->contentsWidth() - kContextMenuMargin : kContextMenuMargin,
kContextMenuMargin);
}
m_frame->view()->setCursor(pointerCursor());
IntPoint position = view->contentsToRootView(location);
IntPoint globalPosition = view->hostWindow()->rootViewToScreen(IntRect(position, IntSize())).location();
Node* targetNode = doc->focusedElement();
if (!targetNode)
targetNode = doc;
// Use the focused node as the target for hover and active.
HitTestResult result(position);
result.setInnerNode(targetNode);
doc->updateHoverActiveState(HitTestRequest::Active | HitTestRequest::ConfusingAndOftenMisusedDisallowShadowContent, result.innerElement());
// The contextmenu event is a mouse event even when invoked using the keyboard.
// This is required for web compatibility.
#if OS(WIN)
PlatformEvent::Type eventType = PlatformEvent::MouseReleased;
#else
PlatformEvent::Type eventType = PlatformEvent::MousePressed;
#endif
PlatformMouseEvent mouseEvent(position, globalPosition, RightButton, eventType, 1, false, false, false, false, WTF::currentTime());
return !dispatchMouseEvent(EventTypeNames::contextmenu, targetNode, true, 0, mouseEvent, false);
}
bool EventHandler::sendContextMenuEventForGesture(const PlatformGestureEvent& event)
{
#if OS(WIN)
PlatformEvent::Type eventType = PlatformEvent::MouseReleased;
#else
PlatformEvent::Type eventType = PlatformEvent::MousePressed;
#endif
IntPoint adjustedPoint = event.position();
adjustGesturePosition(event, adjustedPoint);
PlatformMouseEvent mouseEvent(adjustedPoint, event.globalPosition(), RightButton, eventType, 1, false, false, false, false, WTF::currentTime());
// To simulate right-click behavior, we send a right mouse down and then
// context menu event.
handleMousePressEvent(mouseEvent);
return sendContextMenuEvent(mouseEvent);
// We do not need to send a corresponding mouse release because in case of
// right-click, the context menu takes capture and consumes all events.
}
void EventHandler::scheduleHoverStateUpdate()
{
if (!m_hoverTimer.isActive())
m_hoverTimer.startOneShot(0);
}
void EventHandler::scheduleCursorUpdate()
{
if (!m_cursorUpdateTimer.isActive())
m_cursorUpdateTimer.startOneShot(cursorUpdateInterval);
}
void EventHandler::dispatchFakeMouseMoveEventSoon()
{
if (m_mousePressed)
return;
if (m_mousePositionIsUnknown)
return;
Settings* settings = m_frame->settings();
if (settings && !settings->deviceSupportsMouse())
return;
// If the content has ever taken longer than fakeMouseMoveShortInterval we
// reschedule the timer and use a longer time. This will cause the content
// to receive these moves only after the user is done scrolling, reducing
// pauses during the scroll.
if (m_maxMouseMovedDuration > fakeMouseMoveShortInterval) {
if (m_fakeMouseMoveEventTimer.isActive())
m_fakeMouseMoveEventTimer.stop();
m_fakeMouseMoveEventTimer.startOneShot(fakeMouseMoveLongInterval);
} else {
if (!m_fakeMouseMoveEventTimer.isActive())
m_fakeMouseMoveEventTimer.startOneShot(fakeMouseMoveShortInterval);
}
}
void EventHandler::dispatchFakeMouseMoveEventSoonInQuad(const FloatQuad& quad)
{
FrameView* view = m_frame->view();
if (!view)
return;
if (!quad.containsPoint(view->windowToContents(m_lastKnownMousePosition)))
return;
dispatchFakeMouseMoveEventSoon();
}
void EventHandler::fakeMouseMoveEventTimerFired(Timer<EventHandler>* timer)
{
ASSERT_UNUSED(timer, timer == &m_fakeMouseMoveEventTimer);
ASSERT(!m_mousePressed);
Settings* settings = m_frame->settings();
if (settings && !settings->deviceSupportsMouse())
return;
FrameView* view = m_frame->view();
if (!view)
return;
if (!m_frame->page() || !m_frame->page()->focusController().isActive())
return;
// Don't dispatch a synthetic mouse move event if the mouse cursor is not visible to the user.
if (!isCursorVisible())
return;
bool shiftKey;
bool ctrlKey;
bool altKey;
bool metaKey;
PlatformKeyboardEvent::getCurrentModifierState(shiftKey, ctrlKey, altKey, metaKey);
PlatformMouseEvent fakeMouseMoveEvent(m_lastKnownMousePosition, m_lastKnownMouseGlobalPosition, NoButton, PlatformEvent::MouseMoved, 0, shiftKey, ctrlKey, altKey, metaKey, currentTime());
handleMouseMoveEvent(fakeMouseMoveEvent);
}
void EventHandler::cancelFakeMouseMoveEvent()
{
m_fakeMouseMoveEventTimer.stop();
}
bool EventHandler::isCursorVisible() const
{
return m_frame->page()->isCursorVisible();
}
void EventHandler::setResizingFrameSet(HTMLFrameSetElement* frameSet)
{
m_frameSetBeingResized = frameSet;
}
void EventHandler::resizeScrollableAreaDestroyed()
{
ASSERT(m_resizeScrollableArea);
m_resizeScrollableArea = 0;
}
void EventHandler::hoverTimerFired(Timer<EventHandler>*)
{
m_hoverTimer.stop();
ASSERT(m_frame);
ASSERT(m_frame->document());
if (RenderView* renderer = m_frame->contentRenderer()) {
if (FrameView* view = m_frame->view()) {
HitTestRequest request(HitTestRequest::Move | HitTestRequest::ConfusingAndOftenMisusedDisallowShadowContent);
HitTestResult result(view->windowToContents(m_lastKnownMousePosition));
renderer->hitTest(request, result);
m_frame->document()->updateHoverActiveState(request, result.innerElement());
}
}
}
void EventHandler::activeIntervalTimerFired(Timer<EventHandler>*)
{
m_activeIntervalTimer.stop();
if (m_frame
&& m_frame->document()
&& m_lastDeferredTapElement
&& m_lastDeferredTapElement.get() == m_frame->document()->activeElement()) {
HitTestRequest request(HitTestRequest::Release | HitTestRequest::ConfusingAndOftenMisusedDisallowShadowContent);
m_frame->document()->updateHoverActiveState(request, m_lastDeferredTapElement.get());
}
m_lastDeferredTapElement = 0;
}
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 = 0;
}
bool EventHandler::handleAccessKey(const PlatformKeyboardEvent& evt)
{
// FIXME: Ignoring the state of Shift key is what neither IE nor Firefox do.
// IE matches lower and upper case access keys regardless of Shift key state - but if both upper and
// lower case variants are present in a document, the correct element is matched based on Shift key state.
// Firefox only matches an access key if Shift is not pressed, and does that case-insensitively.
ASSERT(!(accessKeyModifiers() & PlatformEvent::ShiftKey));
if ((evt.modifiers() & ~PlatformEvent::ShiftKey) != accessKeyModifiers())
return false;
String key = evt.unmodifiedText();
Element* elem = m_frame->document()->getElementByAccessKey(key.lower());
if (!elem)
return false;
elem->accessKeyAction(false);
return true;
}
bool EventHandler::isKeyEventAllowedInFullScreen(FullscreenElementStack* fullscreen, const PlatformKeyboardEvent& keyEvent) const
{
if (fullscreen->webkitFullScreenKeyboardInputAllowed())
return true;
if (keyEvent.type() == PlatformKeyboardEvent::Char) {
if (keyEvent.text().length() != 1)
return false;
UChar character = keyEvent.text()[0];
return character == ' ';
}
int keyCode = keyEvent.windowsVirtualKeyCode();
return (keyCode >= VK_BACK && keyCode <= VK_CAPITAL)
|| (keyCode >= VK_SPACE && keyCode <= VK_DELETE)
|| (keyCode >= VK_OEM_1 && keyCode <= VK_OEM_PLUS)
|| (keyCode >= VK_MULTIPLY && keyCode <= VK_OEM_8);
}
bool EventHandler::keyEvent(const PlatformKeyboardEvent& initialKeyEvent)
{
RefPtr<FrameView> protector(m_frame->view());
if (FullscreenElementStack* fullscreen = FullscreenElementStack::fromIfExists(m_frame->document())) {
if (fullscreen->webkitIsFullScreen() && !isKeyEventAllowedInFullScreen(fullscreen, initialKeyEvent))
return false;
}
if (initialKeyEvent.windowsVirtualKeyCode() == VK_CAPITAL)
capsLockStateMayHaveChanged();
#if OS(WIN)
if (panScrollInProgress()) {
// If a key is pressed while the panScroll is in progress then we want to stop
if (initialKeyEvent.type() == PlatformEvent::KeyDown || initialKeyEvent.type() == PlatformEvent::RawKeyDown)
stopAutoscroll();
// If we were in panscroll mode, we swallow the key event
return true;
}
#endif
// 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;
UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture);
if (FrameView* view = m_frame->view())
view->resetDeferredRepaintDelay();
// In IE, access keys are special, they are handled after default keydown processing, but cannot be canceled - this is hard to match.
// On Mac OS X, we process them before dispatching keydown, as the default keydown handler implements Emacs key bindings, which may conflict
// with access keys. Then we dispatch keydown, but suppress its default handling.
// On Windows, WebKit explicitly calls handleAccessKey() instead of dispatching a keypress event for WM_SYSCHAR messages.
// Other platforms currently match either Mac or Windows behavior, depending on whether they send combined KeyDown events.
bool matchedAnAccessKey = false;
if (initialKeyEvent.type() == PlatformEvent::KeyDown)
matchedAnAccessKey = handleAccessKey(initialKeyEvent);
// 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());
if (matchedAnAccessKey)
keydown->setDefaultPrevented(true);
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();
}
static FocusDirection focusDirectionForKey(const AtomicString& keyIdentifier)
{
DEFINE_STATIC_LOCAL(AtomicString, Down, ("Down", AtomicString::ConstructFromLiteral));
DEFINE_STATIC_LOCAL(AtomicString, Up, ("Up", AtomicString::ConstructFromLiteral));
DEFINE_STATIC_LOCAL(AtomicString, Left, ("Left", AtomicString::ConstructFromLiteral));
DEFINE_STATIC_LOCAL(AtomicString, Right, ("Right", AtomicString::ConstructFromLiteral));
FocusDirection retVal = FocusDirectionNone;
if (keyIdentifier == Down)
retVal = FocusDirectionDown;
else if (keyIdentifier == Up)
retVal = FocusDirectionUp;
else if (keyIdentifier == Left)
retVal = FocusDirectionLeft;
else if (keyIdentifier == Right)
retVal = FocusDirectionRight;
return retVal;
}
void EventHandler::defaultKeyboardEventHandler(KeyboardEvent* event)
{
if (event->type() == EventTypeNames::keydown) {
m_frame->editor().handleKeyboardEvent(event);
if (event->defaultHandled())
return;
if (event->keyIdentifier() == "U+0009")
defaultTabEventHandler(event);
else if (event->keyIdentifier() == "U+0008")
defaultBackspaceEventHandler(event);
else if (event->keyIdentifier() == "U+001B")
defaultEscapeEventHandler(event);
else {
FocusDirection direction = focusDirectionForKey(event->keyIdentifier());
if (direction != FocusDirectionNone)
defaultArrowEventHandler(direction, event);
}
}
if (event->type() == EventTypeNames::keypress) {
m_frame->editor().handleKeyboardEvent(event);
if (event->defaultHandled())
return;
if (event->charCode() == ' ')
defaultSpaceEventHandler(event);
}
}
bool EventHandler::dragHysteresisExceeded(const IntPoint& floatDragViewportLocation) const
{
FloatPoint dragViewportLocation(floatDragViewportLocation.x(), floatDragViewportLocation.y());
return dragHysteresisExceeded(dragViewportLocation);
}
bool EventHandler::dragHysteresisExceeded(const FloatPoint& dragViewportLocation) const
{
FrameView* view = m_frame->view();
if (!view)
return false;
IntPoint dragLocation = view->windowToContents(flooredIntPoint(dragViewportLocation));
IntSize delta = dragLocation - m_mouseDownPos;
int threshold = GeneralDragHysteresis;
switch (dragState().m_dragType) {
case DragSourceActionSelection:
threshold = TextDragHysteresis;
break;
case DragSourceActionImage:
threshold = ImageDragHysteresis;
break;
case DragSourceActionLink:
threshold = LinkDragHysteresis;
break;
case DragSourceActionDHTML:
break;
case DragSourceActionNone:
ASSERT_NOT_REACHED();
}
return abs(delta.width()) >= threshold || abs(delta.height()) >= threshold;
}
void EventHandler::freeClipboard()
{
if (dragState().m_dragClipboard)
dragState().m_dragClipboard->setAccessPolicy(ClipboardNumb);
}
void EventHandler::dragSourceEndedAt(const PlatformMouseEvent& event, DragOperation operation)
{
// Send a hit test request so that RenderLayer gets a chance to update the :hover and :active pseudoclasses.
HitTestRequest request(HitTestRequest::Release | HitTestRequest::ConfusingAndOftenMisusedDisallowShadowContent);
prepareMouseEvent(request, event);
if (dragState().m_dragSrc) {
dragState().m_dragClipboard->setDestinationOperation(operation);
// for now we don't care if event handler cancels default behavior, since there is none
dispatchDragSrcEvent(EventTypeNames::dragend, event);
}
freeClipboard();
dragState().m_dragSrc = 0;
// In case the drag was ended due to an escape key press we need to ensure
// that consecutive mousemove events don't reinitiate the drag and drop.
m_mouseDownMayStartDrag = false;
}
void EventHandler::updateDragStateAfterEditDragIfNeeded(Element* rootEditableElement)
{
// If inserting the dragged contents removed the drag source, we still want to fire dragend at the root editble element.
if (dragState().m_dragSrc && !dragState().m_dragSrc->inDocument())
dragState().m_dragSrc = rootEditableElement;
}
// returns if we should continue "default processing", i.e., whether eventhandler canceled
bool EventHandler::dispatchDragSrcEvent(const AtomicString& eventType, const PlatformMouseEvent& event)
{
return !dispatchDragEvent(eventType, dragState().m_dragSrc.get(), event, dragState().m_dragClipboard.get());
}
bool EventHandler::handleDrag(const MouseEventWithHitTestResults& event, CheckDragHysteresis checkDragHysteresis)
{
ASSERT(event.event().type() == PlatformEvent::MouseMoved);
// Callers must protect the reference to FrameView, since this function may dispatch DOM
// events, causing page/FrameView to go away.
ASSERT(m_frame);
ASSERT(m_frame->view());
if (!m_frame->page())
return false;
if (event.event().button() != LeftButton || event.event().type() != PlatformEvent::MouseMoved) {
// If we allowed the other side of the bridge to handle a drag
// last time, then m_mousePressed might still be set. So we
// clear it now to make sure the next move after a drag
// doesn't look like a drag.
m_mousePressed = false;
return false;
}
if (m_mouseDownMayStartDrag) {
HitTestRequest request(HitTestRequest::ReadOnly);
HitTestResult result(m_mouseDownPos);
m_frame->contentRenderer()->hitTest(request, result);
Node* node = result.innerNode();
if (node) {
DragController::SelectionDragPolicy selectionDragPolicy = event.event().timestamp() - m_mouseDownTimestamp < TextDragDelay
? DragController::DelayedSelectionDragResolution
: DragController::ImmediateSelectionDragResolution;
dragState().m_dragSrc = m_frame->page()->dragController().draggableNode(m_frame, node, m_mouseDownPos, selectionDragPolicy, dragState().m_dragType);
} else {
dragState().m_dragSrc = 0;
}
if (!dragState().m_dragSrc)
m_mouseDownMayStartDrag = false; // no element is draggable
}
if (!m_mouseDownMayStartDrag)
return !mouseDownMayStartSelect() && !m_mouseDownMayStartAutoscroll;
// We are starting a text/image/url drag, so the cursor should be an arrow
// FIXME <rdar://7577595>: Custom cursors aren't supported during drag and drop (default to pointer).
m_frame->view()->setCursor(pointerCursor());
if (checkDragHysteresis == ShouldCheckDragHysteresis && !dragHysteresisExceeded(event.event().position()))
return true;
// Once we're past the hysteresis point, we don't want to treat this gesture as a click
invalidateClick();
if (!tryStartDrag(event)) {
// Something failed to start the drag, clean up.
freeClipboard();
dragState().m_dragSrc = 0;
}
m_mouseDownMayStartDrag = false;
// Whether or not the drag actually started, no more default handling (like selection).
return true;
}
bool EventHandler::tryStartDrag(const MouseEventWithHitTestResults& event)
{
freeClipboard(); // would only happen if we missed a dragEnd. Do it anyway, just
// to make sure it gets numbified
dragState().m_dragClipboard = createDraggingClipboard();
// Check to see if this a DOM based drag, if it is get the DOM specified drag
// image and offset
if (dragState().m_dragType == DragSourceActionDHTML) {
if (RenderObject* renderer = dragState().m_dragSrc->renderer()) {
// FIXME: This doesn't work correctly with transforms.
FloatPoint absPos = renderer->localToAbsolute();
IntSize delta = m_mouseDownPos - roundedIntPoint(absPos);
dragState().m_dragClipboard->setDragImageElement(dragState().m_dragSrc.get(), IntPoint(delta));
} else {
// The renderer has disappeared, this can happen if the onStartDrag handler has hidden
// the element in some way. In this case we just kill the drag.
return false;
}
}
DragController& dragController = m_frame->page()->dragController();
if (!dragController.populateDragClipboard(m_frame, dragState(), m_mouseDownPos))
return false;
m_mouseDownMayStartDrag = dispatchDragSrcEvent(EventTypeNames::dragstart, m_mouseDown)
&& !m_frame->selection().isInPasswordField();
// Invalidate clipboard here against anymore pasteboard writing for security. The drag
// image can still be changed as we drag, but not the pasteboard data.
dragState().m_dragClipboard->setAccessPolicy(ClipboardImageWritable);
if (m_mouseDownMayStartDrag) {
// Dispatching the event could cause Page to go away. Make sure it's still valid before trying to use DragController.
m_didStartDrag = m_frame->page() && dragController.startDrag(m_frame, dragState(), event.event(), m_mouseDownPos);
// FIXME: This seems pretty useless now. The gesture code uses this as a signal for
// whether or not the drag started, but perhaps it can simply use the return value from
// handleDrag(), even though it doesn't mean exactly the same thing.
if (m_didStartDrag)
return true;
// Drag was canned at the last minute - we owe m_dragSrc a DRAGEND event
dispatchDragSrcEvent(EventTypeNames::dragend, event.event());
}
return false;
}
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;
if (FrameView* view = m_frame->view())
view->resetDeferredRepaintDelay();
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::defaultSpaceEventHandler(KeyboardEvent* event)
{
ASSERT(event->type() == EventTypeNames::keypress);
if (event->ctrlKey() || event->metaKey() || event->altKey() || event->altGraphKey())
return;
ScrollLogicalDirection direction = event->shiftKey() ? ScrollBlockDirectionBackward : ScrollBlockDirectionForward;
if (logicalScrollOverflow(direction, ScrollByPage)) {
event->setDefaultHandled();
return;
}
FrameView* view = m_frame->view();
if (!view)
return;
if (view->logicalScroll(direction, ScrollByPage))
event->setDefaultHandled();
}
void EventHandler::defaultBackspaceEventHandler(KeyboardEvent* event)
{
ASSERT(event->type() == EventTypeNames::keydown);
if (event->ctrlKey() || event->metaKey() || event->altKey() || event->altGraphKey())
return;
if (!m_frame->editor().behavior().shouldNavigateBackOnBackspace())
return;
Page* page = m_frame->page();
if (!page)
return;
bool handledEvent = page->mainFrame()->loader().client()->navigateBackForward(event->shiftKey() ? 1 : -1);
if (handledEvent)
event->setDefaultHandled();
}
void EventHandler::defaultArrowEventHandler(FocusDirection focusDirection, KeyboardEvent* event)
{
ASSERT(event->type() == EventTypeNames::keydown);
if (event->ctrlKey() || event->metaKey() || event->altGraphKey() || event->shiftKey())
return;
Page* page = m_frame->page();
if (!page)
return;
if (!isSpatialNavigationEnabled(m_frame))
return;
// Arrows and other possible directional navigation keys can be used in design
// mode editing.
if (m_frame->document()->inDesignMode())
return;
if (page->focusController().advanceFocus(focusDirection))
event->setDefaultHandled();
}
void EventHandler::defaultTabEventHandler(KeyboardEvent* event)
{
ASSERT(event->type() == EventTypeNames::keydown);
// We should only advance focus on tabs if no special modifier keys are held down.
if (event->ctrlKey() || event->metaKey() || event->altGraphKey())
return;
Page* page = m_frame->page();
if (!page)
return;
if (!page->tabKeyCyclesThroughElements())
return;
FocusDirection focusDirection = event->shiftKey() ? FocusDirectionBackward : FocusDirectionForward;
// Tabs can be used in design mode editing.
if (m_frame->document()->inDesignMode())
return;
if (page->focusController().advanceFocus(focusDirection))
event->setDefaultHandled();
}
void EventHandler::defaultEscapeEventHandler(KeyboardEvent* event)
{
if (HTMLDialogElement* dialog = m_frame->document()->activeModalDialog())
dialog->dispatchEvent(Event::createCancelable(EventTypeNames::cancel));
}
void EventHandler::capsLockStateMayHaveChanged()
{
if (Element* element = m_frame->document()->focusedElement()) {
if (RenderObject* r = element->renderer()) {
if (r->isTextField())
toRenderTextControlSingleLine(r)->capsLockStateMayHaveChanged();
}
}
}
void EventHandler::sendScrollEvent()
{
setFrameWasScrolledByUser();
if (m_frame->view() && m_frame->document())
m_frame->document()->enqueueScrollEventForNode(m_frame->document());
}
void EventHandler::setFrameWasScrolledByUser()
{
FrameView* v = m_frame->view();
if (v)
v->setWasScrolledByUser(true);
}
bool EventHandler::passMousePressEventToScrollbar(MouseEventWithHitTestResults& mev, Scrollbar* scrollbar)
{
if (!scrollbar || !scrollbar->enabled())
return false;
setFrameWasScrolledByUser();
scrollbar->mouseDown(mev.event());
return true;
}
// 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;
}
}
static const AtomicString& eventNameForTouchPointState(PlatformTouchPoint::State state)
{
switch (state) {
case PlatformTouchPoint::TouchReleased:
return EventTypeNames::touchend;
case PlatformTouchPoint::TouchCancelled:
return EventTypeNames::touchcancel;
case PlatformTouchPoint::TouchPressed:
return EventTypeNames::touchstart;
case PlatformTouchPoint::TouchMoved:
return EventTypeNames::touchmove;
case PlatformTouchPoint::TouchStationary:
// TouchStationary state is not converted to touch events, so fall through to assert.
default:
ASSERT_NOT_REACHED();
return emptyAtom;
}
}
HitTestResult EventHandler::hitTestResultInFrame(Frame* 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;
}
bool EventHandler::handleTouchEvent(const PlatformTouchEvent& event)
{
// First build up the lists to use for the 'touches', 'targetTouches' and 'changedTouches' attributes
// in the JS event. See http://www.sitepen.com/blog/2008/07/10/touching-and-gesturing-on-the-iphone/
// for an overview of how these lists fit together.
// Holds the complete set of touches on the screen and will be used as the 'touches' list in the JS event.
RefPtr<TouchList> touches = TouchList::create();
// A different view on the 'touches' list above, filtered and grouped by event target. Used for the
// 'targetTouches' list in the JS event.
typedef HashMap<EventTarget*, RefPtr<TouchList> > TargetTouchesMap;
TargetTouchesMap touchesByTarget;
// Array of touches per state, used to assemble the 'changedTouches' list in the JS event.
typedef HashSet<RefPtr<EventTarget> > EventTargetSet;
struct {
// The touches corresponding to the particular change state this struct instance represents.
RefPtr<TouchList> m_touches;
// Set of targets involved in m_touches.
EventTargetSet m_targets;
} changedTouches[PlatformTouchPoint::TouchStateEnd];
const Vector<PlatformTouchPoint>& points = event.touchPoints();
UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture);
unsigned i;
bool freshTouchEvents = true;
bool allTouchReleased = true;
for (i = 0; i < points.size(); ++i) {
const PlatformTouchPoint& point = points[i];
if (point.state() != PlatformTouchPoint::TouchPressed)
freshTouchEvents = false;
if (point.state() != PlatformTouchPoint::TouchReleased && point.state() != PlatformTouchPoint::TouchCancelled)
allTouchReleased = false;
}
for (i = 0; i < points.size(); ++i) {
const PlatformTouchPoint& point = points[i];
PlatformTouchPoint::State pointState = point.state();
LayoutPoint pagePoint = documentPointForWindowPoint(m_frame, point.pos());
// Gesture events trigger the active state, not touch events,
// so touch event hit tests can always be read only.
HitTestRequest::HitTestRequestType hitType = HitTestRequest::TouchEvent | HitTestRequest::ReadOnly;
// The HitTestRequest types used for mouse events map quite adequately
// to touch events. Note that in addition to meaning that the hit test
// should affect the active state of the current node if necessary,
// HitTestRequest::Active signifies that the hit test is taking place
// with the mouse (or finger in this case) being pressed.
switch (pointState) {
case PlatformTouchPoint::TouchPressed:
hitType |= HitTestRequest::Active;
break;
case PlatformTouchPoint::TouchMoved:
hitType |= HitTestRequest::Active | HitTestRequest::Move;
break;
case PlatformTouchPoint::TouchReleased:
case PlatformTouchPoint::TouchCancelled:
hitType |= HitTestRequest::Release;
break;
case PlatformTouchPoint::TouchStationary:
hitType |= HitTestRequest::Active;
break;
default:
ASSERT_NOT_REACHED();
break;
}
// Increment the platform touch id by 1 to avoid storing a key of 0 in the hashmap.
unsigned touchPointTargetKey = point.id() + 1;
RefPtr<EventTarget> touchTarget;
if (pointState == PlatformTouchPoint::TouchPressed) {
HitTestResult result;
if (freshTouchEvents) {
result = hitTestResultAtPoint(pagePoint, hitType);
m_originatingTouchPointTargetKey = touchPointTargetKey;
} else if (m_originatingTouchPointDocument.get() && m_originatingTouchPointDocument->frame()) {
LayoutPoint pagePointInOriginatingDocument = documentPointForWindowPoint(m_originatingTouchPointDocument->frame(), point.pos());
result = hitTestResultInFrame(m_originatingTouchPointDocument->frame(), pagePointInOriginatingDocument, hitType);
} else
continue;
Node* node = result.innerNode();
if (!node)
continue;
// Touch events should not go to text nodes
if (node->isTextNode())
node = EventPath::parent(node);
Document& doc = node->document();
// Record the originating touch document even if it does not have a touch listener.
if (freshTouchEvents) {
m_originatingTouchPointDocument = &doc;
freshTouchEvents = false;
}
if (!doc.hasTouchEventHandlers())
continue;
m_originatingTouchPointTargets.set(touchPointTargetKey, node);
touchTarget = node;
// FIXME(rbyers): Should really be doing a second hit test that ignores inline elements - crbug.com/319479.
TouchAction effectiveTouchAction = computeEffectiveTouchAction(*node);
if (effectiveTouchAction != TouchActionAuto)
m_frame->page()->chrome().client().setTouchAction(effectiveTouchAction);
} else if (pointState == PlatformTouchPoint::TouchReleased || pointState == PlatformTouchPoint::TouchCancelled) {
// The target should be the original target for this touch, so get it from the hashmap. As it's a release or cancel
// we also remove it from the map.
touchTarget = m_originatingTouchPointTargets.take(touchPointTargetKey);
} else
// No hittest is performed on move or stationary, since the target is not allowed to change anyway.
touchTarget = m_originatingTouchPointTargets.get(touchPointTargetKey);
if (!touchTarget.get())
continue;
Document& doc = touchTarget->toNode()->document();
if (!doc.hasTouchEventHandlers())
continue;
Frame* targetFrame = doc.frame();
if (!targetFrame)
continue;
if (m_frame != targetFrame) {
// pagePoint should always be relative to the target elements containing frame.
pagePoint = documentPointForWindowPoint(targetFrame, point.pos());
}
float scaleFactor = targetFrame->pageZoomFactor();
int adjustedPageX = lroundf(pagePoint.x() / scaleFactor);
int adjustedPageY = lroundf(pagePoint.y() / scaleFactor);
RefPtr<Touch> touch = Touch::create(targetFrame, touchTarget.get(), point.id(),
point.screenPos().x(), point.screenPos().y(),
adjustedPageX, adjustedPageY,
point.radiusX(), point.radiusY(), point.rotationAngle(), point.force());
// Ensure this target's touch list exists, even if it ends up empty, so it can always be passed to TouchEvent::Create below.
TargetTouchesMap::iterator targetTouchesIterator = touchesByTarget.find(touchTarget.get());
if (targetTouchesIterator == touchesByTarget.end())
targetTouchesIterator = touchesByTarget.set(touchTarget.get(), TouchList::create()).iterator;
// touches and targetTouches should only contain information about touches still on the screen, so if this point is
// released or cancelled it will only appear in the changedTouches list.
if (pointState != PlatformTouchPoint::TouchReleased && pointState != PlatformTouchPoint::TouchCancelled) {
touches->append(touch);
targetTouchesIterator->value->append(touch);
}
// Now build up the correct list for changedTouches.
// Note that any touches that are in the TouchStationary state (e.g. if
// the user had several points touched but did not move them all) should
// never be in the changedTouches list so we do not handle them explicitly here.
// See https://bugs.webkit.org/show_bug.cgi?id=37609 for further discussion
// about the TouchStationary state.
if (pointState != PlatformTouchPoint::TouchStationary) {
ASSERT(pointState < PlatformTouchPoint::TouchStateEnd);
if (!changedTouches[pointState].m_touches)
changedTouches[pointState].m_touches = TouchList::create();
changedTouches[pointState].m_touches->append(touch);
changedTouches[pointState].m_targets.add(touchTarget);
}
}
m_touchPressed = touches->length() > 0;
if (allTouchReleased)
m_originatingTouchPointDocument.clear();
// Now iterate the changedTouches list and m_targets within it, sending events to the targets as required.
bool swallowedEvent = false;
RefPtr<TouchList> emptyList = TouchList::create();
for (unsigned state = 0; state != PlatformTouchPoint::TouchStateEnd; ++state) {
if (!changedTouches[state].m_touches)
continue;
// When sending a touch cancel event, use empty touches and targetTouches lists.
bool isTouchCancelEvent = (state == PlatformTouchPoint::TouchCancelled);
RefPtr<TouchList>& effectiveTouches(isTouchCancelEvent ? emptyList : touches);
const AtomicString& stateName(eventNameForTouchPointState(static_cast<PlatformTouchPoint::State>(state)));
const EventTargetSet& targetsForState = changedTouches[state].m_targets;
for (EventTargetSet::const_iterator it = targetsForState.begin(); it != targetsForState.end(); ++it) {
EventTarget* touchEventTarget = it->get();
RefPtr<TouchList> targetTouches(isTouchCancelEvent ? emptyList : touchesByTarget.get(touchEventTarget));
ASSERT(targetTouches);
RefPtr<TouchEvent> touchEvent =
TouchEvent::create(effectiveTouches.get(), targetTouches.get(), changedTouches[state].m_touches.get(),
stateName, touchEventTarget->toNode()->document().domWindow(),
0, 0, 0, 0, event.ctrlKey(), event.altKey(), event.shiftKey(), event.metaKey());
touchEventTarget->toNode()->dispatchTouchEvent(touchEvent.get());
swallowedEvent = swallowedEvent || touchEvent->defaultPrevented() || touchEvent->defaultHandled();
}
}
return swallowedEvent;
}
bool EventHandler::dispatchSyntheticTouchEventIfEnabled(const PlatformMouseEvent& event)
{
if (!m_frame || !m_frame->settings() || !m_frame->settings()->isTouchEventEmulationEnabled())
return false;
PlatformEvent::Type eventType = event.type();
if (eventType != PlatformEvent::MouseMoved && eventType != PlatformEvent::MousePressed && eventType != PlatformEvent::MouseReleased)
return false;
HitTestRequest request(HitTestRequest::Active | HitTestRequest::ConfusingAndOftenMisusedDisallowShadowContent);
MouseEventWithHitTestResults mev = prepareMouseEvent(request, event);
if (mev.scrollbar() || subframeForHitTestResult(mev))
return false;
// The order is important. This check should follow the subframe test: http://webkit.org/b/111292.
if (eventType == PlatformEvent::MouseMoved && event.button() == NoButton)
return true;
SyntheticSingleTouchEvent touchEvent(event);
if (handleTouchEvent(touchEvent))
return true;
return handleMouseEventAsEmulatedGesture(event);
}
bool EventHandler::handleMouseEventAsEmulatedGesture(const PlatformMouseEvent& event)
{
PlatformEvent::Type eventType = event.type();
if (event.button() != LeftButton || !m_frame->isMainFrame())
return false;
// Simulate pinch / scroll gesture.
const IntPoint& position = event.position();
bool swallowEvent = false;
FrameView* view = m_frame->view();
if (event.shiftKey()) {
// Shift pressed - consider it gesture.
swallowEvent = true;
Page* page = m_frame->page();
float pageScaleFactor = page->pageScaleFactor();
switch (eventType) {
case PlatformEvent::MousePressed:
m_lastSyntheticPinchAnchorCss = adoptPtr(new IntPoint(view->scrollPosition() + position));
m_lastSyntheticPinchAnchorDip = adoptPtr(new IntPoint(position));
m_lastSyntheticPinchAnchorDip->scale(pageScaleFactor, pageScaleFactor);
m_syntheticPageScaleFactor = pageScaleFactor;
break;
case PlatformEvent::MouseMoved:
{
if (!m_lastSyntheticPinchAnchorCss)
break;
float dy = m_lastSyntheticPinchAnchorDip->y() - position.y() * pageScaleFactor;
float magnifyDelta = exp(dy * 0.002f);
float newPageScaleFactor = m_syntheticPageScaleFactor * magnifyDelta;
IntPoint anchorCss(*m_lastSyntheticPinchAnchorDip.get());
anchorCss.scale(1.f / newPageScaleFactor, 1.f / newPageScaleFactor);
page->inspectorController().requestPageScaleFactor(newPageScaleFactor, *m_lastSyntheticPinchAnchorCss.get() - toIntSize(anchorCss));
break;
}
case PlatformEvent::MouseReleased:
m_lastSyntheticPinchAnchorCss.clear();
m_lastSyntheticPinchAnchorDip.clear();
default:
break;
}
} else {
switch (eventType) {
case PlatformEvent::MouseMoved:
{
// Always consume move events.
swallowEvent = true;
int dx = m_lastSyntheticPanLocation ? position.x() - m_lastSyntheticPanLocation->x() : 0;
int dy = m_lastSyntheticPanLocation ? position.y() - m_lastSyntheticPanLocation->y() : 0;
if (dx || dy)
view->scrollBy(IntSize(-dx, -dy));
// Mouse dragged - consider it gesture.
m_lastSyntheticPanLocation = adoptPtr(new IntPoint(position));
break;
}
case PlatformEvent::MouseReleased:
// There was a drag -> gesture.
swallowEvent = !!m_lastSyntheticPanLocation;
m_lastSyntheticPanLocation.clear();
default:
break;
}
}
return swallowEvent;
}
bool EventHandler::handleWheelEventAsEmulatedGesture(const PlatformWheelEvent& event)
{
if (!m_frame || !m_frame->settings() || !m_frame->settings()->isTouchEventEmulationEnabled())
return false;
// Only convert vertical wheel w/ shift into pinch for touch-enabled device convenience.
if (!event.shiftKey() || !event.deltaY())
return false;
Page* page = m_frame->page();
FrameView* view = m_frame->view();
float pageScaleFactor = page->pageScaleFactor();
IntPoint anchorBeforeCss(view->scrollPosition() + event.position());
IntPoint anchorBeforeDip(event.position());
anchorBeforeDip.scale(pageScaleFactor, pageScaleFactor);
float magnifyDelta = exp(event.deltaY() * 0.002f);
float newPageScaleFactor = pageScaleFactor * magnifyDelta;
IntPoint anchorAfterCss(anchorBeforeDip);
anchorAfterCss.scale(1.f / newPageScaleFactor, 1.f / newPageScaleFactor);
page->inspectorController().requestPageScaleFactor(newPageScaleFactor, anchorBeforeCss - toIntSize(anchorAfterCss));
return true;
}
TouchAction EventHandler::computeEffectiveTouchAction(const Node& node)
{
// Optimization to minimize risk of this new feature (behavior should be identical
// since there's no way to get non-default touch-action values).
if (!RuntimeEnabledFeatures::cssTouchActionEnabled())
return TouchActionAuto;
// Start by permitting all actions, then walk the block level elements from
// the target node up to the nearest scrollable ancestor and exclude any
// prohibited actions. For now this is trivial, but when we add more types
// of actions it'll get a little more complex.
for (const Node* curNode = &node; curNode; curNode = NodeRenderingTraversal::parent(curNode)) {
// The spec says only block and SVG elements get touch-action.
// FIXME(rbyers): Add correct support for SVG, crbug.com/247396.
if (RenderObject* renderer = curNode->renderer()) {
if (renderer->isRenderBlockFlow()) {
if (renderer->style()->touchAction() == TouchActionNone)
return TouchActionNone;
}
// If we've reached an ancestor that supports a touch action, search no further.
if (renderer->isBox() && toRenderBox(renderer)->scrollsOverflow())
break;
}
}
return TouchActionAuto;
}
void EventHandler::setLastKnownMousePosition(const PlatformMouseEvent& event)
{
m_mousePositionIsUnknown = false;
m_lastKnownMousePosition = event.position();
m_lastKnownMouseGlobalPosition = event.globalPosition();
}
bool EventHandler::passMousePressEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe)
{
// If we're clicking into a frame that is selected, the frame will appear
// greyed out even though we're clicking on the selection. This looks
// really strange (having the whole frame be greyed out), so we deselect the
// selection.
IntPoint p = m_frame->view()->windowToContents(mev.event().position());
if (m_frame->selection().contains(p)) {
VisiblePosition visiblePos(
mev.targetNode()->renderer()->positionForPoint(mev.localPoint()));
VisibleSelection newSelection(visiblePos);
m_frame->selection().setSelection(newSelection);
}
subframe->eventHandler().handleMousePressEvent(mev.event());
return true;
}
bool EventHandler::passMouseMoveEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe, HitTestResult* hoveredNode)
{
if (m_mouseDownMayStartDrag && !m_mouseDownWasInSubframe)
return false;
subframe->eventHandler().handleMouseMoveOrLeaveEvent(mev.event(), hoveredNode);
return true;
}
bool EventHandler::passMouseReleaseEventToSubframe(MouseEventWithHitTestResults& mev, Frame* subframe)
{
subframe->eventHandler().handleMouseReleaseEvent(mev.event());
return true;
}
bool EventHandler::passWheelEventToWidget(const PlatformWheelEvent& wheelEvent, Widget* widget)
{
// We can sometimes get a null widget! EventHandlerMac handles a null
// widget by returning false, so we do the same.
if (!widget)
return false;
// If not a FrameView, then probably a plugin widget. Those will receive
// the event via an EventTargetNode dispatch when this returns false.
if (!widget->isFrameView())
return false;
return toFrameView(widget)->frame().eventHandler().handleWheelEvent(wheelEvent);
}
bool EventHandler::passWidgetMouseDownEventToWidget(const MouseEventWithHitTestResults& event)
{
// Figure out which view to send the event to.
if (!event.targetNode() || !event.targetNode()->renderer() || !event.targetNode()->renderer()->isWidget())
return false;
return false;
}
PassRefPtr<Clipboard> EventHandler::createDraggingClipboard() const
{
return Clipboard::create(Clipboard::DragAndDrop, ClipboardWritable, ChromiumDataObject::create());
}
void EventHandler::focusDocumentView()
{
Page* page = m_frame->page();
if (!page)
return;
page->focusController().setFocusedFrame(m_frame);
}
unsigned EventHandler::accessKeyModifiers()
{
#if OS(MACOSX)
return PlatformEvent::CtrlKey | PlatformEvent::AltKey;
#else
return PlatformEvent::AltKey;
#endif
}
} // namespace WebCore