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