mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
We use pointer events instead. R=esprehn@chromium.org Review URL: https://codereview.chromium.org/868133003
1277 lines
46 KiB
C++
1277 lines
46 KiB
C++
/*
|
|
* Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved.
|
|
* Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org)
|
|
* Copyright (C) 2012 Digia Plc. and/or its subsidiary(-ies)
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
|
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "sky/engine/config.h"
|
|
#include "sky/engine/core/page/EventHandler.h"
|
|
|
|
#include "gen/sky/core/HTMLNames.h"
|
|
#include "gen/sky/platform/RuntimeEnabledFeatures.h"
|
|
#include "sky/engine/bindings/core/v8/ExceptionStatePlaceholder.h"
|
|
#include "sky/engine/core/dom/Document.h"
|
|
#include "sky/engine/core/dom/DocumentMarkerController.h"
|
|
#include "sky/engine/core/dom/NodeRenderingTraversal.h"
|
|
#include "sky/engine/core/dom/shadow/ShadowRoot.h"
|
|
#include "sky/engine/core/editing/Editor.h"
|
|
#include "sky/engine/core/editing/FrameSelection.h"
|
|
#include "sky/engine/core/editing/TextIterator.h"
|
|
#include "sky/engine/core/editing/htmlediting.h"
|
|
#include "sky/engine/core/events/DOMWindowEventQueue.h"
|
|
#include "sky/engine/core/events/EventPath.h"
|
|
#include "sky/engine/core/events/KeyboardEvent.h"
|
|
#include "sky/engine/core/events/TextEvent.h"
|
|
#include "sky/engine/core/fetch/ImageResource.h"
|
|
#include "sky/engine/core/frame/FrameView.h"
|
|
#include "sky/engine/core/frame/LocalFrame.h"
|
|
#include "sky/engine/core/frame/Settings.h"
|
|
#include "sky/engine/core/loader/FrameLoaderClient.h"
|
|
#include "sky/engine/core/page/AutoscrollController.h"
|
|
#include "sky/engine/core/page/Chrome.h"
|
|
#include "sky/engine/core/page/ChromeClient.h"
|
|
#include "sky/engine/core/page/EditorClient.h"
|
|
#include "sky/engine/core/page/EventWithHitTestResults.h"
|
|
#include "sky/engine/core/page/FocusController.h"
|
|
#include "sky/engine/core/page/Page.h"
|
|
#include "sky/engine/core/rendering/HitTestRequest.h"
|
|
#include "sky/engine/core/rendering/HitTestResult.h"
|
|
#include "sky/engine/core/rendering/RenderLayer.h"
|
|
#include "sky/engine/core/rendering/RenderView.h"
|
|
#include "sky/engine/core/rendering/style/RenderStyle.h"
|
|
#include "sky/engine/platform/PlatformGestureEvent.h"
|
|
#include "sky/engine/platform/PlatformKeyboardEvent.h"
|
|
#include "sky/engine/platform/TraceEvent.h"
|
|
#include "sky/engine/platform/WindowsKeyboardCodes.h"
|
|
#include "sky/engine/platform/geometry/FloatPoint.h"
|
|
#include "sky/engine/platform/graphics/Image.h"
|
|
#include "sky/engine/platform/heap/Handle.h"
|
|
#include "sky/engine/platform/scroll/ScrollAnimator.h"
|
|
#include "sky/engine/platform/scroll/Scrollbar.h"
|
|
#include "sky/engine/wtf/Assertions.h"
|
|
#include "sky/engine/wtf/CurrentTime.h"
|
|
#include "sky/engine/wtf/StdLibExtras.h"
|
|
#include "sky/engine/wtf/TemporaryChange.h"
|
|
|
|
namespace blink {
|
|
|
|
// The amount of time to wait for a cursor update on style and layout changes
|
|
// Set to 50Hz, no need to be faster than common screen refresh rate
|
|
static const double cursorUpdateInterval = 0.02;
|
|
|
|
static const int maximumCursorSize = 128;
|
|
|
|
// It's pretty unlikely that a scale of less than one would ever be used. But all we really
|
|
// need to ensure here is that the scale isn't so small that integer overflow can occur when
|
|
// dividing cursor sizes (limited above) by the scale.
|
|
static const double minimumCursorScale = 0.001;
|
|
|
|
// The minimum amount of time an element stays active after a ShowPress
|
|
// This is roughly 9 frames, which should be long enough to be noticeable.
|
|
static const double minimumActiveInterval = 0.15;
|
|
|
|
enum NoCursorChangeType { NoCursorChange };
|
|
|
|
class OptionalCursor {
|
|
public:
|
|
OptionalCursor(NoCursorChangeType) : m_isCursorChange(false) { }
|
|
OptionalCursor(const Cursor& cursor) : m_isCursorChange(true), m_cursor(cursor) { }
|
|
|
|
bool isCursorChange() const { return m_isCursorChange; }
|
|
const Cursor& cursor() const { ASSERT(m_isCursorChange); return m_cursor; }
|
|
|
|
private:
|
|
bool m_isCursorChange;
|
|
Cursor m_cursor;
|
|
};
|
|
|
|
class MaximumDurationTracker {
|
|
public:
|
|
explicit MaximumDurationTracker(double *maxDuration)
|
|
: m_maxDuration(maxDuration)
|
|
, m_start(monotonicallyIncreasingTime())
|
|
{
|
|
}
|
|
|
|
~MaximumDurationTracker()
|
|
{
|
|
*m_maxDuration = max(*m_maxDuration, monotonicallyIncreasingTime() - m_start);
|
|
}
|
|
|
|
private:
|
|
double* m_maxDuration;
|
|
double m_start;
|
|
};
|
|
|
|
EventHandler::EventHandler(LocalFrame* frame)
|
|
: m_frame(frame)
|
|
, m_mousePressed(false)
|
|
, m_capturesDragging(false)
|
|
, m_mouseDownMayStartSelect(false)
|
|
, m_mouseDownMayStartDrag(false)
|
|
, m_selectionInitiationState(HaveNotStartedSelection)
|
|
, m_hoverTimer(this, &EventHandler::hoverTimerFired)
|
|
, m_cursorUpdateTimer(this, &EventHandler::cursorUpdateTimerFired)
|
|
, m_mouseDownMayStartAutoscroll(false)
|
|
, m_clickCount(0)
|
|
, m_shouldOnlyFireDragOverEvent(false)
|
|
, m_mousePositionIsUnknown(true)
|
|
, m_scrollGestureHandlingNode(nullptr)
|
|
, m_lastGestureScrollOverWidget(false)
|
|
, m_maxMouseMovedDuration(0)
|
|
, m_didStartDrag(false)
|
|
, m_activeIntervalTimer(this, &EventHandler::activeIntervalTimerFired)
|
|
, m_lastShowPressTimestamp(0)
|
|
{
|
|
}
|
|
|
|
EventHandler::~EventHandler()
|
|
{
|
|
}
|
|
|
|
void EventHandler::clear()
|
|
{
|
|
m_hoverTimer.stop();
|
|
m_cursorUpdateTimer.stop();
|
|
m_activeIntervalTimer.stop();
|
|
m_nodeUnderMouse = nullptr;
|
|
m_lastNodeUnderMouse = nullptr;
|
|
m_lastScrollbarUnderMouse = nullptr;
|
|
m_clickCount = 0;
|
|
m_clickNode = nullptr;
|
|
m_dragTarget = nullptr;
|
|
m_shouldOnlyFireDragOverEvent = false;
|
|
m_mousePositionIsUnknown = true;
|
|
m_lastKnownMousePosition = IntPoint();
|
|
m_lastKnownMouseGlobalPosition = IntPoint();
|
|
m_mousePressNode = nullptr;
|
|
m_mousePressed = false;
|
|
m_capturesDragging = false;
|
|
m_previousWheelScrolledNode = nullptr;
|
|
m_scrollGestureHandlingNode = nullptr;
|
|
m_lastGestureScrollOverWidget = false;
|
|
m_previousGestureScrolledNode = nullptr;
|
|
m_scrollbarHandlingScrollGesture = nullptr;
|
|
m_maxMouseMovedDuration = 0;
|
|
m_didStartDrag = false;
|
|
m_mouseDownMayStartSelect = false;
|
|
m_mouseDownMayStartDrag = false;
|
|
m_lastShowPressTimestamp = 0;
|
|
m_lastDeferredTapElement = nullptr;
|
|
}
|
|
|
|
void EventHandler::nodeWillBeRemoved(Node& nodeToBeRemoved)
|
|
{
|
|
if (!nodeToBeRemoved.containsIncludingShadowDOM(m_clickNode.get()))
|
|
return;
|
|
if (nodeToBeRemoved.isInShadowTree()) {
|
|
m_clickNode = nodeToBeRemoved.parentOrShadowHostNode();
|
|
} else {
|
|
// We don't dispatch click events if the mousedown node is removed
|
|
// before a mouseup event. It is compatible with IE and Firefox.
|
|
m_clickNode = nullptr;
|
|
}
|
|
}
|
|
|
|
static inline bool dispatchSelectStart(Node* node)
|
|
{
|
|
if (!node || !node->renderer())
|
|
return true;
|
|
|
|
return node->dispatchEvent(Event::createCancelableBubble(EventTypeNames::selectstart));
|
|
}
|
|
|
|
static VisibleSelection expandSelectionToRespectUserSelectAll(Node* targetNode, const VisibleSelection& selection)
|
|
{
|
|
Node* rootUserSelectAll = Position::rootUserSelectAllForNode(targetNode);
|
|
if (!rootUserSelectAll)
|
|
return selection;
|
|
|
|
VisibleSelection newSelection(selection);
|
|
newSelection.setBase(positionBeforeNode(rootUserSelectAll).upstream(CanCrossEditingBoundary));
|
|
newSelection.setExtent(positionAfterNode(rootUserSelectAll).downstream(CanCrossEditingBoundary));
|
|
|
|
return newSelection;
|
|
}
|
|
|
|
bool EventHandler::updateSelectionForMouseDownDispatchingSelectStart(Node* targetNode, const VisibleSelection& selection, TextGranularity granularity)
|
|
{
|
|
if (Position::nodeIsUserSelectNone(targetNode))
|
|
return false;
|
|
|
|
if (!dispatchSelectStart(targetNode))
|
|
return false;
|
|
|
|
if (selection.isRange())
|
|
m_selectionInitiationState = ExtendedSelection;
|
|
else {
|
|
granularity = CharacterGranularity;
|
|
m_selectionInitiationState = PlacedCaret;
|
|
}
|
|
|
|
m_frame->selection().setNonDirectionalSelectionIfNeeded(selection, granularity);
|
|
|
|
return true;
|
|
}
|
|
|
|
void EventHandler::selectClosestWordFromHitTestResult(const HitTestResult& result, AppendTrailingWhitespace appendTrailingWhitespace)
|
|
{
|
|
Node* innerNode = result.targetNode();
|
|
VisibleSelection newSelection;
|
|
|
|
if (innerNode && innerNode->renderer()) {
|
|
VisiblePosition pos(innerNode->renderer()->positionForPoint(result.localPoint()));
|
|
if (pos.isNotNull()) {
|
|
newSelection = VisibleSelection(pos);
|
|
newSelection.expandUsingGranularity(WordGranularity);
|
|
}
|
|
|
|
if (appendTrailingWhitespace == ShouldAppendTrailingWhitespace && newSelection.isRange())
|
|
newSelection.appendTrailingWhitespace();
|
|
|
|
updateSelectionForMouseDownDispatchingSelectStart(innerNode, expandSelectionToRespectUserSelectAll(innerNode, newSelection), WordGranularity);
|
|
}
|
|
}
|
|
|
|
void EventHandler::selectClosestMisspellingFromHitTestResult(const HitTestResult& result, AppendTrailingWhitespace appendTrailingWhitespace)
|
|
{
|
|
Node* innerNode = result.targetNode();
|
|
VisibleSelection newSelection;
|
|
|
|
if (innerNode && innerNode->renderer()) {
|
|
VisiblePosition pos(innerNode->renderer()->positionForPoint(result.localPoint()));
|
|
Position start = pos.deepEquivalent();
|
|
Position end = pos.deepEquivalent();
|
|
if (pos.isNotNull()) {
|
|
DocumentMarkerVector markers = innerNode->document().markers().markersInRange(makeRange(pos, pos).get(), DocumentMarker::MisspellingMarkers());
|
|
if (markers.size() == 1) {
|
|
start.moveToOffset(markers[0]->startOffset());
|
|
end.moveToOffset(markers[0]->endOffset());
|
|
newSelection = VisibleSelection(start, end);
|
|
}
|
|
}
|
|
|
|
if (appendTrailingWhitespace == ShouldAppendTrailingWhitespace && newSelection.isRange())
|
|
newSelection.appendTrailingWhitespace();
|
|
|
|
updateSelectionForMouseDownDispatchingSelectStart(innerNode, expandSelectionToRespectUserSelectAll(innerNode, newSelection), WordGranularity);
|
|
}
|
|
}
|
|
|
|
void EventHandler::updateSelectionForMouseDrag()
|
|
{
|
|
FrameView* view = m_frame->view();
|
|
if (!view)
|
|
return;
|
|
RenderView* renderer = m_frame->contentRenderer();
|
|
if (!renderer)
|
|
return;
|
|
|
|
HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::Move);
|
|
HitTestResult result(m_lastKnownMousePosition);
|
|
renderer->hitTest(request, result);
|
|
updateSelectionForMouseDrag(result);
|
|
}
|
|
|
|
void EventHandler::updateSelectionForMouseDrag(const HitTestResult& hitTestResult)
|
|
{
|
|
if (!m_mouseDownMayStartSelect)
|
|
return;
|
|
|
|
Node* target = hitTestResult.targetNode();
|
|
if (!target)
|
|
return;
|
|
|
|
VisiblePosition targetPosition = m_frame->selection().selection().visiblePositionRespectingEditingBoundary(hitTestResult.localPoint(), target);
|
|
// Don't modify the selection if we're not on a node.
|
|
if (targetPosition.isNull())
|
|
return;
|
|
|
|
// Restart the selection if this is the first mouse move. This work is usually
|
|
// done in handleMousePressEvent, but not if the mouse press was on an existing selection.
|
|
VisibleSelection newSelection = m_frame->selection().selection();
|
|
|
|
if (m_selectionInitiationState == HaveNotStartedSelection && !dispatchSelectStart(target))
|
|
return;
|
|
|
|
if (m_selectionInitiationState != ExtendedSelection) {
|
|
// Always extend selection here because it's caused by a mouse drag
|
|
m_selectionInitiationState = ExtendedSelection;
|
|
newSelection = VisibleSelection(targetPosition);
|
|
}
|
|
|
|
if (RuntimeEnabledFeatures::userSelectAllEnabled()) {
|
|
Node* rootUserSelectAllForMousePressNode = Position::rootUserSelectAllForNode(m_mousePressNode.get());
|
|
if (rootUserSelectAllForMousePressNode && rootUserSelectAllForMousePressNode == Position::rootUserSelectAllForNode(target)) {
|
|
newSelection.setBase(positionBeforeNode(rootUserSelectAllForMousePressNode).upstream(CanCrossEditingBoundary));
|
|
newSelection.setExtent(positionAfterNode(rootUserSelectAllForMousePressNode).downstream(CanCrossEditingBoundary));
|
|
} else {
|
|
// Reset base for user select all when base is inside user-select-all area and extent < base.
|
|
if (rootUserSelectAllForMousePressNode && comparePositions(target->renderer()->positionForPoint(hitTestResult.localPoint()), m_mousePressNode->renderer()->positionForPoint(m_dragStartPos)) < 0)
|
|
newSelection.setBase(positionAfterNode(rootUserSelectAllForMousePressNode).downstream(CanCrossEditingBoundary));
|
|
|
|
Node* rootUserSelectAllForTarget = Position::rootUserSelectAllForNode(target);
|
|
if (rootUserSelectAllForTarget && m_mousePressNode->renderer() && comparePositions(target->renderer()->positionForPoint(hitTestResult.localPoint()), m_mousePressNode->renderer()->positionForPoint(m_dragStartPos)) < 0)
|
|
newSelection.setExtent(positionBeforeNode(rootUserSelectAllForTarget).upstream(CanCrossEditingBoundary));
|
|
else if (rootUserSelectAllForTarget && m_mousePressNode->renderer())
|
|
newSelection.setExtent(positionAfterNode(rootUserSelectAllForTarget).downstream(CanCrossEditingBoundary));
|
|
else
|
|
newSelection.setExtent(targetPosition);
|
|
}
|
|
} else {
|
|
newSelection.setExtent(targetPosition);
|
|
}
|
|
|
|
if (m_frame->selection().granularity() != CharacterGranularity)
|
|
newSelection.expandUsingGranularity(m_frame->selection().granularity());
|
|
|
|
m_frame->selection().setNonDirectionalSelectionIfNeeded(newSelection, m_frame->selection().granularity(),
|
|
FrameSelection::AdjustEndpointsAtBidiBoundary);
|
|
}
|
|
|
|
AutoscrollController* EventHandler::autoscrollController() const
|
|
{
|
|
if (Page* page = m_frame->page())
|
|
return &page->autoscrollController();
|
|
return 0;
|
|
}
|
|
|
|
HitTestResult EventHandler::hitTestResultAtPoint(const LayoutPoint& point, HitTestRequest::HitTestRequestType hitType, const LayoutSize& padding)
|
|
{
|
|
TRACE_EVENT0("blink", "EventHandler::hitTestResultAtPoint");
|
|
|
|
HitTestResult result(point, padding.height(), padding.width(), padding.height(), padding.width());
|
|
|
|
// RenderView::hitTest causes a layout, and we don't want to hit that until the first
|
|
// layout because until then, there is nothing shown on the screen - the user can't
|
|
// have intentionally clicked on something belonging to this page. Furthermore,
|
|
// mousemove events before the first layout should not lead to a premature layout()
|
|
// happening, which could show a flash of white.
|
|
// See also the similar code in Document::prepareMouseEvent.
|
|
if (!m_frame->contentRenderer() || !m_frame->view() || !m_frame->view()->didFirstLayout())
|
|
return result;
|
|
|
|
// hitTestResultAtPoint is specifically used to hitTest into all frames, thus it always allows child frame content.
|
|
HitTestRequest request(hitType);
|
|
m_frame->contentRenderer()->hitTest(request, result);
|
|
|
|
return result;
|
|
}
|
|
|
|
void EventHandler::stopAutoscroll()
|
|
{
|
|
if (AutoscrollController* controller = autoscrollController())
|
|
controller->stopAutoscroll();
|
|
}
|
|
|
|
Node* EventHandler::mousePressNode() const
|
|
{
|
|
return m_mousePressNode.get();
|
|
}
|
|
|
|
bool EventHandler::scroll(ScrollDirection direction, ScrollGranularity granularity, Node* startNode, Node** stopNode, float delta, IntPoint absolutePoint)
|
|
{
|
|
if (!delta)
|
|
return false;
|
|
|
|
Node* node = startNode;
|
|
|
|
if (!node)
|
|
node = m_frame->document()->focusedElement();
|
|
|
|
if (!node)
|
|
node = m_mousePressNode.get();
|
|
|
|
if (!node || !node->renderer())
|
|
return false;
|
|
|
|
RenderBox* curBox = node->renderer()->enclosingBox();
|
|
while (curBox && !curBox->isRenderView()) {
|
|
// If we're at the stopNode, we should try to scroll it but we shouldn't bubble past it
|
|
bool shouldStopBubbling = stopNode && *stopNode && curBox->node() == *stopNode;
|
|
bool didScroll = curBox->scroll(direction, granularity, delta);
|
|
|
|
if (didScroll && stopNode)
|
|
*stopNode = curBox->node();
|
|
|
|
if (didScroll || shouldStopBubbling) {
|
|
return true;
|
|
}
|
|
|
|
curBox = curBox->containingBlock();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool EventHandler::bubblingScroll(ScrollDirection direction, ScrollGranularity granularity, Node* startingNode)
|
|
{
|
|
// The layout needs to be up to date to determine if we can scroll. We may be
|
|
// here because of an onLoad event, in which case the final layout hasn't been performed yet.
|
|
m_frame->document()->updateLayout();
|
|
if (scroll(direction, granularity, startingNode))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
IntPoint EventHandler::lastKnownMousePosition() const
|
|
{
|
|
return m_lastKnownMousePosition;
|
|
}
|
|
|
|
bool EventHandler::useHandCursor(Node* node, bool isOverLink)
|
|
{
|
|
if (!node)
|
|
return false;
|
|
|
|
return isOverLink && !node->hasEditableStyle();
|
|
}
|
|
|
|
void EventHandler::cursorUpdateTimerFired(Timer<EventHandler>*)
|
|
{
|
|
ASSERT(m_frame);
|
|
ASSERT(m_frame->document());
|
|
|
|
updateCursor();
|
|
}
|
|
|
|
void EventHandler::updateCursor()
|
|
{
|
|
if (m_mousePositionIsUnknown)
|
|
return;
|
|
|
|
FrameView* view = m_frame->view();
|
|
if (!view || !view->shouldSetCursor())
|
|
return;
|
|
|
|
RenderView* renderView = view->renderView();
|
|
if (!renderView)
|
|
return;
|
|
|
|
m_frame->document()->updateLayout();
|
|
|
|
HitTestRequest request(HitTestRequest::ReadOnly);
|
|
HitTestResult result(m_lastKnownMousePosition);
|
|
renderView->hitTest(request, result);
|
|
|
|
OptionalCursor optionalCursor = selectCursor(result);
|
|
if (optionalCursor.isCursorChange()) {
|
|
m_currentMouseCursor = optionalCursor.cursor();
|
|
view->setCursor(m_currentMouseCursor);
|
|
}
|
|
}
|
|
|
|
OptionalCursor EventHandler::selectCursor(const HitTestResult& result)
|
|
{
|
|
Page* page = m_frame->page();
|
|
if (!page)
|
|
return NoCursorChange;
|
|
|
|
Node* node = result.innerPossiblyPseudoNode();
|
|
if (!node)
|
|
return selectAutoCursor(result, node, iBeamCursor());
|
|
|
|
RenderObject* renderer = node->renderer();
|
|
RenderStyle* style = renderer ? renderer->style() : 0;
|
|
|
|
if (renderer) {
|
|
Cursor overrideCursor;
|
|
switch (renderer->getCursor(roundedIntPoint(result.localPoint()), overrideCursor)) {
|
|
case SetCursorBasedOnStyle:
|
|
break;
|
|
case SetCursor:
|
|
return overrideCursor;
|
|
case DoNotSetCursor:
|
|
return NoCursorChange;
|
|
}
|
|
}
|
|
|
|
if (style && style->cursors()) {
|
|
const CursorList* cursors = style->cursors();
|
|
for (unsigned i = 0; i < cursors->size(); ++i) {
|
|
StyleImage* styleImage = (*cursors)[i].image();
|
|
if (!styleImage)
|
|
continue;
|
|
ImageResource* cachedImage = styleImage->cachedImage();
|
|
if (!cachedImage)
|
|
continue;
|
|
float scale = styleImage->imageScaleFactor();
|
|
// Get hotspot and convert from logical pixels to physical pixels.
|
|
IntPoint hotSpot = (*cursors)[i].hotSpot();
|
|
hotSpot.scale(scale, scale);
|
|
IntSize size = cachedImage->imageForRenderer(renderer)->size();
|
|
if (cachedImage->errorOccurred())
|
|
continue;
|
|
// Limit the size of cursors (in UI pixels) so that they cannot be
|
|
// used to cover UI elements in chrome.
|
|
size.scale(1 / scale);
|
|
if (size.width() > maximumCursorSize || size.height() > maximumCursorSize)
|
|
continue;
|
|
|
|
Image* image = cachedImage->imageForRenderer(renderer);
|
|
// Ensure no overflow possible in calculations above.
|
|
if (scale < minimumCursorScale)
|
|
continue;
|
|
return Cursor(image, hotSpot, scale);
|
|
}
|
|
}
|
|
|
|
switch (style ? style->cursor() : CURSOR_AUTO) {
|
|
case CURSOR_AUTO: {
|
|
const Cursor& iBeam = iBeamCursor();
|
|
return selectAutoCursor(result, node, iBeam);
|
|
}
|
|
case CURSOR_CROSS:
|
|
return crossCursor();
|
|
case CURSOR_POINTER:
|
|
return handCursor();
|
|
case CURSOR_MOVE:
|
|
return moveCursor();
|
|
case CURSOR_ALL_SCROLL:
|
|
return moveCursor();
|
|
case CURSOR_E_RESIZE:
|
|
return eastResizeCursor();
|
|
case CURSOR_W_RESIZE:
|
|
return westResizeCursor();
|
|
case CURSOR_N_RESIZE:
|
|
return northResizeCursor();
|
|
case CURSOR_S_RESIZE:
|
|
return southResizeCursor();
|
|
case CURSOR_NE_RESIZE:
|
|
return northEastResizeCursor();
|
|
case CURSOR_SW_RESIZE:
|
|
return southWestResizeCursor();
|
|
case CURSOR_NW_RESIZE:
|
|
return northWestResizeCursor();
|
|
case CURSOR_SE_RESIZE:
|
|
return southEastResizeCursor();
|
|
case CURSOR_NS_RESIZE:
|
|
return northSouthResizeCursor();
|
|
case CURSOR_EW_RESIZE:
|
|
return eastWestResizeCursor();
|
|
case CURSOR_NESW_RESIZE:
|
|
return northEastSouthWestResizeCursor();
|
|
case CURSOR_NWSE_RESIZE:
|
|
return northWestSouthEastResizeCursor();
|
|
case CURSOR_COL_RESIZE:
|
|
return columnResizeCursor();
|
|
case CURSOR_ROW_RESIZE:
|
|
return rowResizeCursor();
|
|
case CURSOR_TEXT:
|
|
return iBeamCursor();
|
|
case CURSOR_WAIT:
|
|
return waitCursor();
|
|
case CURSOR_HELP:
|
|
return helpCursor();
|
|
case CURSOR_VERTICAL_TEXT:
|
|
return verticalTextCursor();
|
|
case CURSOR_CELL:
|
|
return cellCursor();
|
|
case CURSOR_CONTEXT_MENU:
|
|
return contextMenuCursor();
|
|
case CURSOR_PROGRESS:
|
|
return progressCursor();
|
|
case CURSOR_NO_DROP:
|
|
return noDropCursor();
|
|
case CURSOR_ALIAS:
|
|
return aliasCursor();
|
|
case CURSOR_COPY:
|
|
return copyCursor();
|
|
case CURSOR_NONE:
|
|
return noneCursor();
|
|
case CURSOR_NOT_ALLOWED:
|
|
return notAllowedCursor();
|
|
case CURSOR_DEFAULT:
|
|
return pointerCursor();
|
|
case CURSOR_ZOOM_IN:
|
|
return zoomInCursor();
|
|
case CURSOR_ZOOM_OUT:
|
|
return zoomOutCursor();
|
|
case CURSOR_WEBKIT_GRAB:
|
|
return grabCursor();
|
|
case CURSOR_WEBKIT_GRABBING:
|
|
return grabbingCursor();
|
|
}
|
|
return pointerCursor();
|
|
}
|
|
|
|
OptionalCursor EventHandler::selectAutoCursor(const HitTestResult& result, Node* node, const Cursor& iBeam)
|
|
{
|
|
bool editable = (node && node->hasEditableStyle());
|
|
|
|
if (useHandCursor(node, result.isOverLink()))
|
|
return handCursor();
|
|
|
|
// During selection, use an I-beam no matter what we're over.
|
|
// If a drag may be starting or we're capturing mouse events for a particular node, don't treat this as a selection.
|
|
if (m_mousePressed && m_mouseDownMayStartSelect
|
|
&& !m_mouseDownMayStartDrag
|
|
&& m_frame->selection().isCaretOrRange()) {
|
|
return iBeam;
|
|
}
|
|
|
|
RenderObject* renderer = node ? node->renderer() : 0;
|
|
if ((editable || (renderer && renderer->isText() && node->canStartSelection())) && !result.scrollbar())
|
|
return iBeam;
|
|
return pointerCursor();
|
|
}
|
|
|
|
ScrollableArea* EventHandler::associatedScrollableArea(const RenderLayer* layer) const
|
|
{
|
|
if (RenderLayerScrollableArea* scrollableArea = layer->scrollableArea()) {
|
|
if (scrollableArea->scrollsOverflow())
|
|
return scrollableArea;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void EventHandler::invalidateClick()
|
|
{
|
|
m_clickCount = 0;
|
|
m_clickNode = nullptr;
|
|
}
|
|
|
|
bool EventHandler::isInsideScrollbar(const IntPoint& windowPoint) const
|
|
{
|
|
if (RenderView* renderView = m_frame->contentRenderer()) {
|
|
HitTestRequest request(HitTestRequest::ReadOnly);
|
|
HitTestResult result(windowPoint);
|
|
renderView->hitTest(request, result);
|
|
return result.scrollbar();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool EventHandler::handleGestureShowPress()
|
|
{
|
|
m_lastShowPressTimestamp = WTF::currentTime();
|
|
|
|
FrameView* view = m_frame->view();
|
|
if (!view)
|
|
return false;
|
|
const FrameView::ScrollableAreaSet* areas = view->scrollableAreas();
|
|
if (!areas)
|
|
return false;
|
|
for (FrameView::ScrollableAreaSet::const_iterator it = areas->begin(); it != areas->end(); ++it) {
|
|
ScrollableArea* sa = *it;
|
|
ScrollAnimator* animator = sa->existingScrollAnimator();
|
|
if (animator)
|
|
animator->cancelAnimations();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool EventHandler::handleGestureEvent(const PlatformGestureEvent& gestureEvent)
|
|
{
|
|
TRACE_EVENT0("input", "EventHandler::handleGestureEvent");
|
|
|
|
// Scrolling-related gesture events invoke EventHandler recursively for each frame down
|
|
// the chain, doing a single-frame hit-test per frame. This matches handleWheelEvent.
|
|
// Perhaps we could simplify things by rewriting scroll handling to work inner frame
|
|
// out, and then unify with other gesture events.
|
|
if (gestureEvent.isScrollEvent())
|
|
return handleGestureScrollEvent(gestureEvent);
|
|
|
|
// Non-scrolling related gesture events instead do a single cross-frame hit-test and
|
|
// jump directly to the inner most frame. This matches handleMousePressEvent etc.
|
|
|
|
// Hit test across all frames and do touch adjustment as necessary for the event type.
|
|
GestureEventWithHitTestResults targetedEvent = targetGestureEvent(gestureEvent);
|
|
|
|
// Route to the correct frame.
|
|
if (LocalFrame* innerFrame = targetedEvent.hitTestResult().innerNodeFrame())
|
|
return innerFrame->eventHandler().handleGestureEventInFrame(targetedEvent);
|
|
|
|
// No hit test result, handle in root instance. Perhaps we should just return false instead?
|
|
return handleGestureEventInFrame(targetedEvent);
|
|
}
|
|
|
|
bool EventHandler::handleGestureEventInFrame(const GestureEventWithHitTestResults& targetedEvent)
|
|
{
|
|
ASSERT(!targetedEvent.event().isScrollEvent());
|
|
|
|
RefPtr<Node> eventTarget = targetedEvent.hitTestResult().targetNode();
|
|
RefPtr<Scrollbar> scrollbar = targetedEvent.hitTestResult().scrollbar();
|
|
const PlatformGestureEvent& gestureEvent = targetedEvent.event();
|
|
|
|
if (scrollbar) {
|
|
bool eventSwallowed = scrollbar->gestureEvent(gestureEvent);
|
|
if (gestureEvent.type() == PlatformEvent::GestureTapDown && eventSwallowed)
|
|
m_scrollbarHandlingScrollGesture = scrollbar;
|
|
if (eventSwallowed)
|
|
return true;
|
|
}
|
|
|
|
if (eventTarget && eventTarget->dispatchGestureEvent(gestureEvent))
|
|
return true;
|
|
|
|
switch (gestureEvent.type()) {
|
|
case PlatformEvent::GestureTwoFingerTap: // FIXME(sky): Remove this.
|
|
case PlatformEvent::GestureTap:
|
|
return handleGestureTap(targetedEvent);
|
|
case PlatformEvent::GestureShowPress:
|
|
return handleGestureShowPress();
|
|
case PlatformEvent::GestureLongPress:
|
|
return handleGestureLongPress(targetedEvent);
|
|
case PlatformEvent::GestureLongTap:
|
|
return handleGestureLongTap(targetedEvent);
|
|
case PlatformEvent::GestureTapDown:
|
|
case PlatformEvent::GesturePinchBegin:
|
|
case PlatformEvent::GesturePinchEnd:
|
|
case PlatformEvent::GesturePinchUpdate:
|
|
case PlatformEvent::GestureTapDownCancel:
|
|
case PlatformEvent::GestureTapUnconfirmed:
|
|
break;
|
|
default:
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool EventHandler::handleGestureScrollEvent(const PlatformGestureEvent& gestureEvent)
|
|
{
|
|
RefPtr<Node> eventTarget = nullptr;
|
|
RefPtr<Scrollbar> scrollbar;
|
|
if (gestureEvent.type() != PlatformEvent::GestureScrollBegin) {
|
|
scrollbar = m_scrollbarHandlingScrollGesture.get();
|
|
eventTarget = m_scrollGestureHandlingNode.get();
|
|
}
|
|
|
|
if (!eventTarget) {
|
|
Document* document = m_frame->document();
|
|
if (!document->renderView())
|
|
return false;
|
|
|
|
LayoutPoint viewPoint = gestureEvent.position();
|
|
HitTestRequest request(HitTestRequest::ReadOnly);
|
|
HitTestResult result(viewPoint);
|
|
document->renderView()->hitTest(request, result);
|
|
|
|
eventTarget = result.innerNode();
|
|
|
|
m_lastGestureScrollOverWidget = result.isOverWidget();
|
|
m_scrollGestureHandlingNode = eventTarget;
|
|
m_previousGestureScrolledNode = nullptr;
|
|
|
|
if (!scrollbar)
|
|
scrollbar = result.scrollbar();
|
|
}
|
|
|
|
if (scrollbar) {
|
|
bool eventSwallowed = scrollbar->gestureEvent(gestureEvent);
|
|
if (gestureEvent.type() == PlatformEvent::GestureScrollEnd
|
|
|| gestureEvent.type() == PlatformEvent::GestureFlingStart
|
|
|| !eventSwallowed) {
|
|
m_scrollbarHandlingScrollGesture = nullptr;
|
|
}
|
|
if (eventSwallowed)
|
|
return true;
|
|
}
|
|
|
|
if (eventTarget && eventTarget->dispatchGestureEvent(gestureEvent))
|
|
return true;
|
|
|
|
switch (gestureEvent.type()) {
|
|
case PlatformEvent::GestureScrollBegin:
|
|
return handleGestureScrollBegin(gestureEvent);
|
|
case PlatformEvent::GestureScrollUpdate:
|
|
case PlatformEvent::GestureScrollUpdateWithoutPropagation:
|
|
return handleGestureScrollUpdate(gestureEvent);
|
|
case PlatformEvent::GestureScrollEnd:
|
|
return handleGestureScrollEnd(gestureEvent);
|
|
case PlatformEvent::GestureFlingStart:
|
|
case PlatformEvent::GesturePinchBegin:
|
|
case PlatformEvent::GesturePinchEnd:
|
|
case PlatformEvent::GesturePinchUpdate:
|
|
return false;
|
|
default:
|
|
ASSERT_NOT_REACHED();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool EventHandler::handleGestureTap(const GestureEventWithHitTestResults& targetedEvent)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool EventHandler::handleGestureLongPress(const GestureEventWithHitTestResults& targetedEvent)
|
|
{
|
|
const PlatformGestureEvent& gestureEvent = targetedEvent.event();
|
|
|
|
// FIXME: Ideally we should try to remove the extra mouse-specific hit-tests here (re-using the
|
|
// supplied HitTestResult), but that will require some overhaul of the touch drag-and-drop code
|
|
// and LongPress is such a special scenario that it's unlikely to matter much in practice.
|
|
|
|
IntPoint hitTestPoint = gestureEvent.position();
|
|
HitTestResult result = hitTestResultAtPoint(hitTestPoint);
|
|
Node* innerNode = result.targetNode();
|
|
if (!result.isLiveLink() && innerNode && (innerNode->isContentEditable() || innerNode->isTextNode())) {
|
|
selectClosestWordFromHitTestResult(result, DontAppendTrailingWhitespace);
|
|
if (m_frame->selection().isRange()) {
|
|
focusDocumentView();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool EventHandler::handleGestureLongTap(const GestureEventWithHitTestResults& targetedEvent)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool EventHandler::passScrollGestureEventToWidget(const PlatformGestureEvent& gestureEvent, RenderObject* renderer)
|
|
{
|
|
ASSERT(gestureEvent.isScrollEvent());
|
|
|
|
// FIXME(sky): Remove this.
|
|
return false;
|
|
}
|
|
|
|
bool EventHandler::handleGestureScrollEnd(const PlatformGestureEvent& gestureEvent) {
|
|
RefPtr<Node> node = m_scrollGestureHandlingNode;
|
|
clearGestureScrollNodes();
|
|
|
|
if (node)
|
|
passScrollGestureEventToWidget(gestureEvent, node->renderer());
|
|
|
|
return false;
|
|
}
|
|
|
|
bool EventHandler::handleGestureScrollBegin(const PlatformGestureEvent& gestureEvent)
|
|
{
|
|
Document* document = m_frame->document();
|
|
if (!document->renderView())
|
|
return false;
|
|
|
|
FrameView* view = m_frame->view();
|
|
if (!view)
|
|
return false;
|
|
|
|
// If there's no renderer on the node, send the event to the nearest ancestor with a renderer.
|
|
// Needed for <option> and <optgroup> elements so we can touch scroll <select>s
|
|
while (m_scrollGestureHandlingNode && !m_scrollGestureHandlingNode->renderer())
|
|
m_scrollGestureHandlingNode = m_scrollGestureHandlingNode->parentOrShadowHostNode();
|
|
|
|
if (!m_scrollGestureHandlingNode)
|
|
return false;
|
|
|
|
passScrollGestureEventToWidget(gestureEvent, m_scrollGestureHandlingNode->renderer());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool EventHandler::handleGestureScrollUpdate(const PlatformGestureEvent& gestureEvent)
|
|
{
|
|
FloatSize delta(gestureEvent.deltaX(), gestureEvent.deltaY());
|
|
if (delta.isZero())
|
|
return false;
|
|
|
|
Node* node = m_scrollGestureHandlingNode.get();
|
|
if (!node)
|
|
return sendScrollEventToView(gestureEvent, delta);
|
|
|
|
// Ignore this event if the targeted node does not have a valid renderer.
|
|
RenderObject* renderer = node->renderer();
|
|
if (!renderer)
|
|
return false;
|
|
|
|
RefPtr<FrameView> protector(m_frame->view());
|
|
|
|
Node* stopNode = 0;
|
|
bool scrollShouldNotPropagate = gestureEvent.type() == PlatformEvent::GestureScrollUpdateWithoutPropagation;
|
|
|
|
// Try to send the event to the correct view.
|
|
if (passScrollGestureEventToWidget(gestureEvent, renderer)) {
|
|
if(scrollShouldNotPropagate)
|
|
m_previousGestureScrolledNode = m_scrollGestureHandlingNode;
|
|
|
|
return true;
|
|
}
|
|
|
|
if (scrollShouldNotPropagate)
|
|
stopNode = m_previousGestureScrolledNode.get();
|
|
|
|
// First try to scroll the closest scrollable RenderBox ancestor of |node|.
|
|
ScrollGranularity granularity = ScrollByPixel;
|
|
bool horizontalScroll = scroll(ScrollLeft, granularity, node, &stopNode, delta.width());
|
|
bool verticalScroll = scroll(ScrollUp, granularity, node, &stopNode, delta.height());
|
|
|
|
if (scrollShouldNotPropagate)
|
|
m_previousGestureScrolledNode = stopNode;
|
|
|
|
if (horizontalScroll || verticalScroll) {
|
|
return true;
|
|
}
|
|
|
|
// Otherwise try to scroll the view.
|
|
return sendScrollEventToView(gestureEvent, delta);
|
|
}
|
|
|
|
bool EventHandler::sendScrollEventToView(const PlatformGestureEvent& gestureEvent, const FloatSize& scaledDelta)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void EventHandler::clearGestureScrollNodes()
|
|
{
|
|
m_scrollGestureHandlingNode = nullptr;
|
|
m_previousGestureScrolledNode = nullptr;
|
|
}
|
|
|
|
bool EventHandler::isScrollbarHandlingGestures() const
|
|
{
|
|
return m_scrollbarHandlingScrollGesture.get();
|
|
}
|
|
|
|
GestureEventWithHitTestResults EventHandler::targetGestureEvent(const PlatformGestureEvent& gestureEvent, bool readOnly)
|
|
{
|
|
// Scrolling events get hit tested per frame (like wheel events do).
|
|
ASSERT(!gestureEvent.isScrollEvent());
|
|
|
|
HitTestRequest::HitTestRequestType hitType = getHitTypeForGestureType(gestureEvent.type());
|
|
double activeInterval = 0;
|
|
bool shouldKeepActiveForMinInterval = false;
|
|
if (readOnly) {
|
|
hitType |= HitTestRequest::ReadOnly;
|
|
} else if (gestureEvent.type() == PlatformEvent::GestureTap) {
|
|
// If the Tap is received very shortly after ShowPress, we want to
|
|
// delay clearing of the active state so that it's visible to the user
|
|
// for at least a couple of frames.
|
|
activeInterval = WTF::currentTime() - m_lastShowPressTimestamp;
|
|
shouldKeepActiveForMinInterval = m_lastShowPressTimestamp && activeInterval < minimumActiveInterval;
|
|
if (shouldKeepActiveForMinInterval)
|
|
hitType |= HitTestRequest::ReadOnly;
|
|
}
|
|
|
|
// Perform the rect-based hit-test. Note that we don't yet apply hover/active state here
|
|
// because we need to resolve touch adjustment first so that we apply hover/active it to
|
|
// the final adjusted node.
|
|
IntPoint hitTestPoint = gestureEvent.position();
|
|
IntSize touchRadius = gestureEvent.area();
|
|
touchRadius.scale(1.f / 2);
|
|
// FIXME: We should not do a rect-based hit-test if touch adjustment is disabled.
|
|
HitTestResult hitTestResult = hitTestResultAtPoint(hitTestPoint, hitType | HitTestRequest::ReadOnly, touchRadius);
|
|
|
|
// Now apply hover/active state to the final target.
|
|
// FIXME: This is supposed to send mouseenter/mouseleave events, but doesn't because we
|
|
// aren't passing a PlatformMouseEvent.
|
|
HitTestRequest request(hitType);
|
|
|
|
if (shouldKeepActiveForMinInterval) {
|
|
m_lastDeferredTapElement = hitTestResult.innerElement();
|
|
m_activeIntervalTimer.startOneShot(minimumActiveInterval - activeInterval, FROM_HERE);
|
|
}
|
|
|
|
return GestureEventWithHitTestResults(gestureEvent, hitTestResult);
|
|
}
|
|
|
|
HitTestRequest::HitTestRequestType EventHandler::getHitTypeForGestureType(PlatformEvent::Type type)
|
|
{
|
|
HitTestRequest::HitTestRequestType hitType = HitTestRequest::ReadOnly;
|
|
switch (type) {
|
|
case PlatformEvent::GestureShowPress:
|
|
case PlatformEvent::GestureTapUnconfirmed:
|
|
return hitType | HitTestRequest::Active;
|
|
case PlatformEvent::GestureTapDownCancel:
|
|
// A TapDownCancel received when no element is active shouldn't really be changing hover state.
|
|
if (!m_frame->document()->activeHoverElement())
|
|
hitType |= HitTestRequest::ReadOnly;
|
|
return hitType | HitTestRequest::Release;
|
|
case PlatformEvent::GestureTap:
|
|
return hitType | HitTestRequest::Release;
|
|
case PlatformEvent::GestureTapDown:
|
|
case PlatformEvent::GestureLongPress:
|
|
case PlatformEvent::GestureLongTap:
|
|
case PlatformEvent::GestureTwoFingerTap:
|
|
// FIXME: Shouldn't LongTap and TwoFingerTap clear the Active state?
|
|
return hitType | HitTestRequest::Active | HitTestRequest::ReadOnly;
|
|
default:
|
|
ASSERT_NOT_REACHED();
|
|
return hitType | HitTestRequest::Active | HitTestRequest::ReadOnly;
|
|
}
|
|
}
|
|
|
|
void EventHandler::scheduleHoverStateUpdate()
|
|
{
|
|
if (!m_hoverTimer.isActive())
|
|
m_hoverTimer.startOneShot(0, FROM_HERE);
|
|
}
|
|
|
|
void EventHandler::scheduleCursorUpdate()
|
|
{
|
|
if (!m_cursorUpdateTimer.isActive())
|
|
m_cursorUpdateTimer.startOneShot(cursorUpdateInterval, FROM_HERE);
|
|
}
|
|
|
|
bool EventHandler::isCursorVisible() const
|
|
{
|
|
return m_frame->page()->isCursorVisible();
|
|
}
|
|
|
|
void EventHandler::hoverTimerFired(Timer<EventHandler>*)
|
|
{
|
|
m_hoverTimer.stop();
|
|
|
|
ASSERT(m_frame);
|
|
ASSERT(m_frame->document());
|
|
|
|
if (RenderView* renderer = m_frame->contentRenderer()) {
|
|
HitTestRequest request(HitTestRequest::Move);
|
|
HitTestResult result(m_lastKnownMousePosition);
|
|
renderer->hitTest(request, result);
|
|
}
|
|
}
|
|
|
|
void EventHandler::activeIntervalTimerFired(Timer<EventHandler>*)
|
|
{
|
|
m_activeIntervalTimer.stop();
|
|
m_lastDeferredTapElement = nullptr;
|
|
}
|
|
|
|
void EventHandler::notifyElementActivated()
|
|
{
|
|
// Since another element has been set to active, stop current timer and clear reference.
|
|
if (m_activeIntervalTimer.isActive())
|
|
m_activeIntervalTimer.stop();
|
|
m_lastDeferredTapElement = nullptr;
|
|
}
|
|
|
|
bool EventHandler::keyEvent(const PlatformKeyboardEvent& initialKeyEvent)
|
|
{
|
|
RefPtr<FrameView> protector(m_frame->view());
|
|
|
|
if (initialKeyEvent.windowsVirtualKeyCode() == VK_CAPITAL)
|
|
capsLockStateMayHaveChanged();
|
|
|
|
// Check for cases where we are too early for events -- possible unmatched key up
|
|
// from pressing return in the location bar.
|
|
RefPtr<Node> node = eventTargetNodeForDocument(m_frame->document());
|
|
if (!node)
|
|
return false;
|
|
|
|
// FIXME: it would be fair to let an input method handle KeyUp events before DOM dispatch.
|
|
if (initialKeyEvent.type() == PlatformEvent::KeyUp || initialKeyEvent.type() == PlatformEvent::Char)
|
|
return !node->dispatchKeyEvent(initialKeyEvent);
|
|
|
|
PlatformKeyboardEvent keyDownEvent = initialKeyEvent;
|
|
if (keyDownEvent.type() != PlatformEvent::RawKeyDown)
|
|
keyDownEvent.disambiguateKeyDownEvent(PlatformEvent::RawKeyDown);
|
|
RefPtr<KeyboardEvent> keydown = KeyboardEvent::create(keyDownEvent, m_frame->document()->domWindow());
|
|
|
|
keydown->setTarget(node);
|
|
|
|
if (initialKeyEvent.type() == PlatformEvent::RawKeyDown) {
|
|
node->dispatchEvent(keydown, IGNORE_EXCEPTION);
|
|
// If frame changed as a result of keydown dispatch, then return true to avoid sending a subsequent keypress message to the new frame.
|
|
bool changedFocusedFrame = m_frame->page() && m_frame != m_frame->page()->focusController().focusedOrMainFrame();
|
|
return keydown->defaultHandled() || keydown->defaultPrevented() || changedFocusedFrame;
|
|
}
|
|
|
|
node->dispatchEvent(keydown, IGNORE_EXCEPTION);
|
|
// If frame changed as a result of keydown dispatch, then return early to avoid sending a subsequent keypress message to the new frame.
|
|
bool changedFocusedFrame = m_frame->page() && m_frame != m_frame->page()->focusController().focusedOrMainFrame();
|
|
bool keydownResult = keydown->defaultHandled() || keydown->defaultPrevented() || changedFocusedFrame;
|
|
if (keydownResult)
|
|
return keydownResult;
|
|
|
|
// Focus may have changed during keydown handling, so refetch node.
|
|
// But if we are dispatching a fake backward compatibility keypress, then we pretend that the keypress happened on the original node.
|
|
node = eventTargetNodeForDocument(m_frame->document());
|
|
if (!node)
|
|
return false;
|
|
|
|
PlatformKeyboardEvent keyPressEvent = initialKeyEvent;
|
|
keyPressEvent.disambiguateKeyDownEvent(PlatformEvent::Char);
|
|
if (keyPressEvent.text().isEmpty())
|
|
return keydownResult;
|
|
RefPtr<KeyboardEvent> keypress = KeyboardEvent::create(keyPressEvent, m_frame->document()->domWindow());
|
|
keypress->setTarget(node);
|
|
if (keydownResult)
|
|
keypress->setDefaultPrevented(true);
|
|
node->dispatchEvent(keypress, IGNORE_EXCEPTION);
|
|
|
|
return keydownResult || keypress->defaultPrevented() || keypress->defaultHandled();
|
|
}
|
|
|
|
void EventHandler::defaultKeyboardEventHandler(KeyboardEvent* event)
|
|
{
|
|
if (event->type() == EventTypeNames::keydown) {
|
|
// Clear caret blinking suspended state to make sure that caret blinks
|
|
// when we type again after long pressing on an empty input field.
|
|
if (m_frame && m_frame->selection().isCaretBlinkingSuspended())
|
|
m_frame->selection().setCaretBlinkingSuspended(false);
|
|
|
|
m_frame->editor().handleKeyboardEvent(event);
|
|
if (event->defaultHandled())
|
|
return;
|
|
if (event->keyIdentifier() == "U+0009")
|
|
defaultTabEventHandler(event);
|
|
}
|
|
if (event->type() == EventTypeNames::keypress) {
|
|
m_frame->editor().handleKeyboardEvent(event);
|
|
if (event->defaultHandled())
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool EventHandler::dragHysteresisExceeded(const FloatPoint& floatDragViewportLocation) const
|
|
{
|
|
return dragHysteresisExceeded(flooredIntPoint(floatDragViewportLocation));
|
|
}
|
|
|
|
bool EventHandler::dragHysteresisExceeded(const IntPoint& dragViewportLocation) const
|
|
{
|
|
FrameView* view = m_frame->view();
|
|
if (!view)
|
|
return false;
|
|
IntPoint dragLocation = dragViewportLocation;
|
|
IntSize delta = dragLocation - m_mouseDownPos;
|
|
|
|
int threshold = 3;
|
|
|
|
return abs(delta.width()) >= threshold || abs(delta.height()) >= threshold;
|
|
}
|
|
|
|
bool EventHandler::handleTextInputEvent(const String& text, Event* underlyingEvent, TextEventInputType inputType)
|
|
{
|
|
// Platforms should differentiate real commands like selectAll from text input in disguise (like insertNewline),
|
|
// and avoid dispatching text input events from keydown default handlers.
|
|
ASSERT(!underlyingEvent || !underlyingEvent->isKeyboardEvent() || toKeyboardEvent(underlyingEvent)->type() == EventTypeNames::keypress);
|
|
|
|
if (!m_frame)
|
|
return false;
|
|
|
|
EventTarget* target;
|
|
if (underlyingEvent)
|
|
target = underlyingEvent->target();
|
|
else
|
|
target = eventTargetNodeForDocument(m_frame->document());
|
|
if (!target)
|
|
return false;
|
|
|
|
RefPtr<TextEvent> event = TextEvent::create(m_frame->domWindow(), text, inputType);
|
|
event->setUnderlyingEvent(underlyingEvent);
|
|
|
|
target->dispatchEvent(event, IGNORE_EXCEPTION);
|
|
return event->defaultHandled();
|
|
}
|
|
|
|
void EventHandler::defaultTextInputEventHandler(TextEvent* event)
|
|
{
|
|
if (m_frame->editor().handleTextEvent(event))
|
|
event->setDefaultHandled();
|
|
}
|
|
|
|
void EventHandler::defaultTabEventHandler(KeyboardEvent* event)
|
|
{
|
|
}
|
|
|
|
void EventHandler::capsLockStateMayHaveChanged()
|
|
{
|
|
}
|
|
|
|
// If scrollbar (under mouse) is different from last, send a mouse exited. Set
|
|
// last to scrollbar if setLast is true; else set last to 0.
|
|
void EventHandler::updateLastScrollbarUnderMouse(Scrollbar* scrollbar, bool setLast)
|
|
{
|
|
if (m_lastScrollbarUnderMouse != scrollbar) {
|
|
// Send mouse exited to the old scrollbar.
|
|
if (m_lastScrollbarUnderMouse)
|
|
m_lastScrollbarUnderMouse->mouseExited();
|
|
|
|
// Send mouse entered if we're setting a new scrollbar.
|
|
if (scrollbar && setLast)
|
|
scrollbar->mouseEntered();
|
|
|
|
m_lastScrollbarUnderMouse = setLast ? scrollbar : 0;
|
|
}
|
|
}
|
|
|
|
HitTestResult EventHandler::hitTestResultInFrame(LocalFrame* frame, const LayoutPoint& point, HitTestRequest::HitTestRequestType hitType)
|
|
{
|
|
HitTestResult result(point);
|
|
|
|
if (!frame || !frame->contentRenderer())
|
|
return result;
|
|
if (frame->view()) {
|
|
IntRect rect = frame->view()->visibleContentRect();
|
|
if (!rect.contains(roundedIntPoint(point)))
|
|
return result;
|
|
}
|
|
frame->contentRenderer()->hitTest(HitTestRequest(hitType), result);
|
|
return result;
|
|
}
|
|
|
|
TouchAction EventHandler::intersectTouchAction(TouchAction action1, TouchAction action2)
|
|
{
|
|
if (action1 == TouchActionNone || action2 == TouchActionNone)
|
|
return TouchActionNone;
|
|
if (action1 == TouchActionAuto)
|
|
return action2;
|
|
if (action2 == TouchActionAuto)
|
|
return action1;
|
|
if (!(action1 & action2))
|
|
return TouchActionNone;
|
|
return action1 & action2;
|
|
}
|
|
|
|
TouchAction EventHandler::computeEffectiveTouchAction(const Node& node)
|
|
{
|
|
// Start by permitting all actions, then walk the elements supporting
|
|
// touch-action from the target node up to the nearest scrollable ancestor
|
|
// and exclude any prohibited actions.
|
|
TouchAction effectiveTouchAction = TouchActionAuto;
|
|
for (const Node* curNode = &node; curNode; curNode = NodeRenderingTraversal::parent(curNode)) {
|
|
if (RenderObject* renderer = curNode->renderer()) {
|
|
if (renderer->supportsTouchAction()) {
|
|
TouchAction action = renderer->style()->touchAction();
|
|
effectiveTouchAction = intersectTouchAction(action, effectiveTouchAction);
|
|
if (effectiveTouchAction == TouchActionNone)
|
|
break;
|
|
}
|
|
|
|
// If we've reached an ancestor that supports a touch action, search no further.
|
|
if (renderer->isBox() && toRenderBox(renderer)->scrollsOverflow())
|
|
break;
|
|
}
|
|
}
|
|
return effectiveTouchAction;
|
|
}
|
|
|
|
void EventHandler::focusDocumentView()
|
|
{
|
|
Page* page = m_frame->page();
|
|
if (!page)
|
|
return;
|
|
page->focusController().focusDocumentView(m_frame);
|
|
}
|
|
|
|
} // namespace blink
|