| /* |
| * 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 |