mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
There's only one bindings system now. TBR=eseidel@chromium.org Review URL: https://codereview.chromium.org/915293003
545 lines
19 KiB
C++
545 lines
19 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/exception_state_placeholder.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/ChromeClient.h"
|
|
#include "sky/engine/core/page/EditorClient.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/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();
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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()))
|
|
return iBeam;
|
|
return pointerCursor();
|
|
}
|
|
|
|
void EventHandler::invalidateClick()
|
|
{
|
|
m_clickCount = 0;
|
|
m_clickNode = nullptr;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
return effectiveTouchAction;
|
|
}
|
|
|
|
void EventHandler::focusDocumentView()
|
|
{
|
|
Page* page = m_frame->page();
|
|
if (!page)
|
|
return;
|
|
page->focusController().focusDocumentView(m_frame);
|
|
}
|
|
|
|
} // namespace blink
|