| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "sky/engine/config.h" |
| #include "sky/engine/core/frame/NewEventHandler.h" |
| |
| #include "sky/engine/core/dom/Document.h" |
| #include "sky/engine/core/dom/NodeRenderingTraversal.h" |
| #include "sky/engine/core/editing/Editor.h" |
| #include "sky/engine/core/editing/FrameSelection.h" |
| #include "sky/engine/core/editing/htmlediting.h" |
| #include "sky/engine/core/events/GestureEvent.h" |
| #include "sky/engine/core/events/KeyboardEvent.h" |
| #include "sky/engine/core/events/PointerEvent.h" |
| #include "sky/engine/core/events/WheelEvent.h" |
| #include "sky/engine/core/frame/LocalFrame.h" |
| #include "sky/engine/core/frame/FrameView.h" |
| #include "sky/engine/core/rendering/RenderObject.h" |
| #include "sky/engine/core/rendering/RenderView.h" |
| #include "sky/engine/platform/KeyboardCodes.h" |
| #include "sky/engine/platform/geometry/FloatPoint.h" |
| #include "sky/engine/public/platform/WebInputEvent.h" |
| |
| namespace blink { |
| |
| static VisiblePosition visiblePositionForHitTestResult(const HitTestResult& hitTestResult) |
| { |
| Node* innerNode = hitTestResult.innerNode(); |
| VisiblePosition position(innerNode->renderer()->positionForPoint(hitTestResult.localPoint())); |
| if (!position.isNull()) |
| return position; |
| return VisiblePosition(firstPositionInOrBeforeNode(innerNode), DOWNSTREAM); |
| } |
| |
| template<typename EventType> |
| static LayoutPoint positionForEvent(const EventType& event) |
| { |
| return roundedLayoutPoint(FloatPoint(event.x, event.y)); |
| } |
| |
| NewEventHandler::NewEventHandler(LocalFrame& frame) |
| : m_frame(frame) |
| , m_suppressNextCharEvent(false) |
| { |
| } |
| |
| NewEventHandler::~NewEventHandler() |
| { |
| } |
| |
| Node* NewEventHandler::targetForKeyboardEvent() const |
| { |
| Document* document = m_frame.document(); |
| if (Node* focusedElement = document->focusedElement()) |
| return focusedElement; |
| return document->documentElement(); |
| } |
| |
| Node* NewEventHandler::targetForHitTestResult(const HitTestResult& hitTestResult) |
| { |
| Node* node = hitTestResult.innerNode(); |
| if (!node) |
| return m_frame.document()->documentElement(); |
| if (node->isTextNode()) |
| return NodeRenderingTraversal::parent(node); |
| return node; |
| } |
| |
| HitTestResult NewEventHandler::performHitTest(const LayoutPoint& point) |
| { |
| HitTestResult result(point); |
| if (!m_frame.contentRenderer()) |
| return result; |
| m_frame.contentRenderer()->hitTest(HitTestRequest(HitTestRequest::ReadOnly), result); |
| return result; |
| } |
| |
| bool NewEventHandler::dispatchPointerEvent(Node& target, const WebPointerEvent& event) |
| { |
| RefPtr<PointerEvent> pointerEvent = PointerEvent::create(event); |
| // TODO(abarth): Keep track of how many pointers are targeting the same node |
| // and only mark the first one as primary. |
| return target.dispatchEvent(pointerEvent.release()); |
| } |
| |
| bool NewEventHandler::dispatchGestureEvent(Node& target, const WebGestureEvent& event) |
| { |
| RefPtr<GestureEvent> gestureEvent = GestureEvent::create(event); |
| return target.dispatchEvent(gestureEvent.release()); |
| } |
| |
| bool NewEventHandler::dispatchKeyboardEvent(Node& target, const WebKeyboardEvent& event) |
| { |
| RefPtr<KeyboardEvent> keyboardEvent = KeyboardEvent::create(event); |
| return target.dispatchEvent(keyboardEvent.release()); |
| } |
| |
| bool NewEventHandler::dispatchWheelEvent(Node& target, const WebWheelEvent& event) |
| { |
| RefPtr<WheelEvent> wheelEvent = WheelEvent::create(event); |
| return target.dispatchEvent(wheelEvent.release()); |
| } |
| |
| bool NewEventHandler::dispatchClickEvent(Node& capturingTarget, const WebPointerEvent& event) |
| { |
| ASSERT(event.type == WebInputEvent::PointerUp); |
| HitTestResult hitTestResult = performHitTest(positionForEvent(event)); |
| Node* releaseTarget = targetForHitTestResult(hitTestResult); |
| Node* clickTarget = NodeRenderingTraversal::commonAncestor(*releaseTarget, capturingTarget); |
| if (!clickTarget) |
| return true; |
| // TODO(abarth): Make a proper gesture event that includes information from the event. |
| return clickTarget->dispatchEvent(Event::createCancelableBubble(EventTypeNames::click)); |
| } |
| |
| void NewEventHandler::updateSelectionForPointerDown(const HitTestResult& hitTestResult, const WebPointerEvent& event) |
| { |
| Node* innerNode = hitTestResult.innerNode(); |
| if (!innerNode->renderer()) |
| return; |
| if (Position::nodeIsUserSelectNone(innerNode)) |
| return; |
| if (!innerNode->dispatchEvent(Event::createCancelableBubble(EventTypeNames::selectstart))) |
| return; |
| VisiblePosition position = visiblePositionForHitTestResult(hitTestResult); |
| // TODO(abarth): Can we change this to setSelectionIfNeeded? |
| m_frame.selection().setNonDirectionalSelectionIfNeeded(VisibleSelection(position), CharacterGranularity); |
| } |
| |
| bool NewEventHandler::handlePointerEvent(const WebPointerEvent& event) |
| { |
| if (event.type == WebInputEvent::PointerDown) |
| return handlePointerDownEvent(event); |
| if (event.type == WebInputEvent::PointerUp) |
| return handlePointerUpEvent(event); |
| if (event.type == WebInputEvent::PointerMove) |
| return handlePointerMoveEvent(event); |
| ASSERT(event.type == WebInputEvent::PointerCancel); |
| return handlePointerCancelEvent(event); |
| } |
| |
| bool NewEventHandler::handleGestureEvent(const WebGestureEvent& event) |
| { |
| HitTestResult hitTestResult = performHitTest(positionForEvent(event)); |
| RefPtr<Node> target = targetForHitTestResult(hitTestResult); |
| return target && !dispatchGestureEvent(*target, event); |
| } |
| |
| bool NewEventHandler::handleKeyboardEvent(const WebKeyboardEvent& event) |
| { |
| bool shouldSuppressCharEvent = m_suppressNextCharEvent; |
| m_suppressNextCharEvent = false; |
| |
| if (event.type == WebInputEvent::Char) { |
| if (shouldSuppressCharEvent) |
| return true; |
| // Do we really need to suppress keypress events for these keys anymore? |
| if (event.key == VKEY_BACK |
| || event.key == VKEY_ESCAPE) |
| return true; |
| } |
| |
| RefPtr<Node> target = targetForKeyboardEvent(); |
| bool handled = target && !dispatchKeyboardEvent(*target, event); |
| |
| // If the keydown event was handled, we don't want to "generate" a keypress |
| // event for that keystroke. However, we'll receive a Char event from the |
| // embedder regardless, so we set m_suppressNextCharEvent, will will prevent |
| // us from dispatching the keypress event when we receive that Char event. |
| if (handled && event.type == WebInputEvent::KeyDown) |
| m_suppressNextCharEvent = true; |
| |
| return handled; |
| } |
| |
| bool NewEventHandler::handleWheelEvent(const WebWheelEvent& event) |
| { |
| HitTestResult hitTestResult = performHitTest(positionForEvent(event)); |
| RefPtr<Node> target = targetForHitTestResult(hitTestResult); |
| return target && !dispatchWheelEvent(*target, event); |
| } |
| |
| bool NewEventHandler::handlePointerDownEvent(const WebPointerEvent& event) |
| { |
| // In principle, we shouldn't get another pointer down for the same |
| // pointer ID, but for mice, we don't get a pointer cancel when you |
| // drag outside the window frame on Linux. For now, send the pointer |
| // cancel at this point. |
| if (event.kind == WebPointerEvent::Mouse |
| && m_targetForPointer.find(event.pointer) != m_targetForPointer.end()) { |
| WebPointerEvent fakeCancel = event; |
| fakeCancel.type = WebInputEvent::PointerCancel; |
| handlePointerCancelEvent(fakeCancel); |
| } |
| |
| ASSERT(m_targetForPointer.find(event.pointer) == m_targetForPointer.end()); |
| HitTestResult hitTestResult = performHitTest(positionForEvent(event)); |
| RefPtr<Node> target = targetForHitTestResult(hitTestResult); |
| if (!target) |
| return false; |
| m_targetForPointer[event.pointer] = target; |
| bool eventSwallowed = !dispatchPointerEvent(*target, event); |
| // TODO(abarth): Set the target for the pointer to something determined when |
| // dispatching the event. |
| updateSelectionForPointerDown(hitTestResult, event); |
| return eventSwallowed; |
| } |
| |
| bool NewEventHandler::handlePointerUpEvent(const WebPointerEvent& event) |
| { |
| auto it = m_targetForPointer.find(event.pointer); |
| if (it == m_targetForPointer.end()) |
| return false; |
| RefPtr<Node> target = it->second; |
| m_targetForPointer.erase(it); |
| ASSERT(target); |
| bool eventSwallowed = !dispatchPointerEvent(*target, event); |
| // When the user releases the primary pointer, we need to dispatch a tap |
| // event to the common ancestor for where the pointer went down and where |
| // it came up. |
| if (!dispatchClickEvent(*target, event)) |
| eventSwallowed = true; |
| return eventSwallowed; |
| } |
| |
| bool NewEventHandler::handlePointerMoveEvent(const WebPointerEvent& event) |
| { |
| auto it = m_targetForPointer.find(event.pointer); |
| if (it == m_targetForPointer.end()) |
| return false; |
| RefPtr<Node> target = it->second; |
| ASSERT(target); |
| return dispatchPointerEvent(*target.get(), event); |
| } |
| |
| bool NewEventHandler::handlePointerCancelEvent(const WebPointerEvent& event) |
| { |
| auto it = m_targetForPointer.find(event.pointer); |
| if (it == m_targetForPointer.end()) |
| return false; |
| RefPtr<Node> target = it->second; |
| m_targetForPointer.erase(it); |
| ASSERT(target); |
| return dispatchPointerEvent(*target, event); |
| } |
| |
| } |