flutter_flutter/engine/core/page/EventHandler.cpp
Adam Barth 3ddae5ea36 Switch KeyboardEvents over to NewEventHandler
This CL moves KeyboardEvents from the old event model to NewEventHandler. This
CL keeps the basic structure of keydown, keypress, keyup events even though
that's a bit wacky. As with pointer and gesture events, this CL removes
PlatformKeyboardEvent in favor of just using WebKeyboardEvent. I've also made
WebKeyboardEvent align more closely with Mojo's keyboard event.

The CL does change one important aspect of key event handling: on the web the
"keyCode" property of KeyboardEvent changes its meaning depending on whether
the event is a keydown or a keypress event. For the former events, keyCode is
the "virtual" (i.e., windows) key code where for the latter events, keyCode is
the character code. To be more precise, I've renamed keyCode to virtualKeyCode
and I've given it a zero (unknown key code) value during keypress events.

R=ojan@chromium.org, eseidel@chromium.org

Review URL: https://codereview.chromium.org/872233002
2015-01-25 22:46:50 -08:00

631 lines
21 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/TraceEvent.h"
#include "sky/engine/platform/KeyboardCodes.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;
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_capturesDragging(false)
, m_selectionInitiationState(HaveNotStartedSelection)
, m_cursorUpdateTimer(this, &EventHandler::cursorUpdateTimerFired)
, m_clickCount(0)
, m_shouldOnlyFireDragOverEvent(false)
, m_didStartDrag(false)
, m_activeIntervalTimer(this, &EventHandler::activeIntervalTimerFired)
, m_lastShowPressTimestamp(0)
{
}
EventHandler::~EventHandler()
{
}
void EventHandler::clear()
{
m_cursorUpdateTimer.stop();
m_activeIntervalTimer.stop();
m_clickCount = 0;
m_clickNode = nullptr;
m_dragTarget = nullptr;
m_shouldOnlyFireDragOverEvent = false;
m_capturesDragging = false;
m_didStartDrag = 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;
}
}
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();
}
}
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();
}
}
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();
}
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->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;
}
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()
{
}
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();
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;
}
void EventHandler::scheduleCursorUpdate()
{
if (!m_cursorUpdateTimer.isActive())
m_cursorUpdateTimer.startOneShot(cursorUpdateInterval, FROM_HERE);
}
bool EventHandler::isCursorVisible() const
{
return m_frame->page()->isCursorVisible();
}
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;
}
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->key() == VKEY_TAB)
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
{
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;
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()
{
}
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