blob: 085d29e51e92e34d827ffb29d10e9c156e7fdbd2 [file] [log] [blame]
/*
* Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved.
* Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org)
* Copyright (C) 2012 Digia Plc. and/or its subsidiary(-ies)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "sky/engine/config.h"
#include "sky/engine/core/page/EventHandler.h"
#include "gen/sky/core/HTMLNames.h"
#include "gen/sky/platform/RuntimeEnabledFeatures.h"
#include "sky/engine/bindings/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;
}
}
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::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);
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: {
return selectAutoCursor(result, node);
}
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)
{
RenderObject* renderer = node ? node->renderer() : 0;
if (!node || !renderer)
return pointerCursor();
if (node->hasEditableStyle() || (renderer->isText() && node->canStartSelection()))
return iBeamCursor();
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;
}
// TODO(abarth): This should just be targetForKeyboardEvent
static Node* eventTargetNodeForDocument(Document* document)
{
if (Node* node = document->focusedElement())
return node;
return document;
}
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