diff options
author | Simon Hausmann <simon.hausmann@nokia.com> | 2012-02-24 16:36:50 +0100 |
---|---|---|
committer | Simon Hausmann <simon.hausmann@nokia.com> | 2012-02-24 16:36:50 +0100 |
commit | ad0d549d4cc13433f77c1ac8f0ab379c83d93f28 (patch) | |
tree | b34b0daceb7c8e7fdde4b4ec43650ab7caadb0a9 /Source/WebKit/blackberry/WebKitSupport/TouchEventHandler.cpp | |
parent | 03e12282df9aa1e1fb05a8b90f1cfc2e08764cec (diff) | |
download | qtwebkit-ad0d549d4cc13433f77c1ac8f0ab379c83d93f28.tar.gz |
Imported WebKit commit bb52bf3c0119e8a128cd93afe5572413a8617de9 (http://svn.webkit.org/repository/webkit/trunk@108790)
Diffstat (limited to 'Source/WebKit/blackberry/WebKitSupport/TouchEventHandler.cpp')
-rw-r--r-- | Source/WebKit/blackberry/WebKitSupport/TouchEventHandler.cpp | 405 |
1 files changed, 405 insertions, 0 deletions
diff --git a/Source/WebKit/blackberry/WebKitSupport/TouchEventHandler.cpp b/Source/WebKit/blackberry/WebKitSupport/TouchEventHandler.cpp new file mode 100644 index 000000000..cfe559af0 --- /dev/null +++ b/Source/WebKit/blackberry/WebKitSupport/TouchEventHandler.cpp @@ -0,0 +1,405 @@ +/* + * Copyright (C) 2010, 2011, 2012 Research In Motion Limited. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" +#include "TouchEventHandler.h" + +#include "DOMSupport.h" +#include "Document.h" +#include "DocumentMarkerController.h" +#include "FocusController.h" +#include "Frame.h" +#include "FrameView.h" +#include "HTMLAnchorElement.h" +#include "HTMLAreaElement.h" +#include "HTMLImageElement.h" +#include "HTMLInputElement.h" +#include "HTMLNames.h" +#include "HTMLPlugInElement.h" +#include "InputHandler.h" +#include "IntRect.h" +#include "IntSize.h" +#include "Node.h" +#include "Page.h" +#include "PlatformMouseEvent.h" +#include "PlatformTouchEvent.h" +#include "RenderLayer.h" +#include "RenderTheme.h" +#include "RenderView.h" +#include "RenderedDocumentMarker.h" +#include "SelectionHandler.h" +#include "WebPage_p.h" + +#include <wtf/MathExtras.h> + +using namespace WebCore; +using namespace WTF; + +namespace BlackBerry { +namespace WebKit { + +static bool hasMouseMoveListener(Element* element) +{ + ASSERT(element); + return element->hasEventListeners(eventNames().mousemoveEvent) || element->document()->hasEventListeners(eventNames().mousemoveEvent); +} + +static bool hasTouchListener(Element* element) +{ + ASSERT(element); + return element->hasEventListeners(eventNames().touchstartEvent) + || element->hasEventListeners(eventNames().touchmoveEvent) + || element->hasEventListeners(eventNames().touchcancelEvent) + || element->hasEventListeners(eventNames().touchendEvent); +} + +static bool elementExpectsMouseEvents(Element* element) +{ + // Make sure we are not operating a shadow node here, since the webpages + // aren't able to attach event listeners to shadow content. + ASSERT(element); + while (element->isInShadowTree()) + element = toElement(element->shadowAncestorNode()); + + return hasMouseMoveListener(element) && !hasTouchListener(element); +} + +static bool shouldConvertTouchToMouse(Element* element) +{ + if (!element) + return false; + + // Range element are a special case that require natural mouse events in order to allow + // dragging of the slider handle. + if (element->hasTagName(HTMLNames::inputTag)) { + HTMLInputElement* inputElement = static_cast<HTMLInputElement*>(element); + if (inputElement->isRangeControl()) + return true; + } + + if ((element->hasTagName(HTMLNames::objectTag) || element->hasTagName(HTMLNames::embedTag)) && static_cast<HTMLPlugInElement*>(element)) + return true; + + // Check if the element has a mouse listener and no touch listener. If so, + // the field will require touch events be converted to mouse events to function properly. + if (elementExpectsMouseEvents(element)) + return true; + + return false; +} + +TouchEventHandler::TouchEventHandler(WebPagePrivate* webpage) + : m_webPage(webpage) + , m_didCancelTouch(false) + , m_convertTouchToMouse(false) + , m_existingTouchMode(ProcessedTouchEvents) +{ +} + +TouchEventHandler::~TouchEventHandler() +{ +} + +bool TouchEventHandler::shouldSuppressMouseDownOnTouchDown() const +{ + return m_lastFatFingersResult.isTextInput() || m_webPage->m_inputHandler->isInputMode() || m_webPage->m_selectionHandler->isSelectionActive(); +} + +void TouchEventHandler::touchEventCancel() +{ + m_webPage->m_inputHandler->processPendingKeyboardVisibilityChange(); + + if (!shouldSuppressMouseDownOnTouchDown()) { + // Input elements delay mouse down and do not need to be released on touch cancel. + m_webPage->m_page->focusController()->focusedOrMainFrame()->eventHandler()->setMousePressed(false); + } + m_convertTouchToMouse = false; + m_didCancelTouch = true; + + // If we cancel a single touch event, we need to also clean up any hover + // state we get into by synthetically moving the mouse to the m_fingerPoint. + Element* elementUnderFatFinger = m_lastFatFingersResult.nodeAsElementIfApplicable(); + if (elementUnderFatFinger && elementUnderFatFinger->renderer()) { + + HitTestRequest request(HitTestRequest::FingerUp); + // The HitTestResult point is not actually needed. + HitTestResult result(IntPoint::zero()); + result.setInnerNode(elementUnderFatFinger); + + Document* document = elementUnderFatFinger->document(); + ASSERT(document); + document->renderView()->layer()->updateHoverActiveState(request, result); + document->updateStyleIfNeeded(); + // Updating the document style may destroy the renderer. + if (elementUnderFatFinger->renderer()) + elementUnderFatFinger->renderer()->repaint(); + ASSERT(!elementUnderFatFinger->hovered()); + } + + m_lastFatFingersResult.reset(); +} + +void TouchEventHandler::touchEventCancelAndClearFocusedNode() +{ + touchEventCancel(); + m_webPage->clearFocusNode(); +} + +void TouchEventHandler::touchHoldEvent() +{ + // This is a hack for our hack that converts the touch pressed event that we've delayed because the user has focused a input field + // to the page as a mouse pressed event. + if (shouldSuppressMouseDownOnTouchDown()) + handleFatFingerPressed(); + + // Clear the focus ring indication if tap-and-hold'ing on a link. + if (m_lastFatFingersResult.node() && m_lastFatFingersResult.node()->isLink()) + m_webPage->clearFocusNode(); +} + +bool TouchEventHandler::handleTouchPoint(Platform::TouchPoint& point) +{ + switch (point.m_state) { + case Platform::TouchPoint::TouchPressed: + { + m_lastFatFingersResult.reset(); // Theoretically this shouldn't be required. Keep it just in case states get mangled. + m_didCancelTouch = false; + m_lastScreenPoint = point.m_screenPos; + + IntPoint contentPos(m_webPage->mapFromViewportToContents(point.m_pos)); + + m_lastFatFingersResult = FatFingers(m_webPage, contentPos, FatFingers::ClickableElement).findBestPoint(); + + Element* elementUnderFatFinger = 0; + if (m_lastFatFingersResult.positionWasAdjusted() && m_lastFatFingersResult.node()) { + ASSERT(m_lastFatFingersResult.node()->isElementNode()); + elementUnderFatFinger = m_lastFatFingersResult.nodeAsElementIfApplicable(); + } + + // Set or reset the touch mode. + Element* possibleTargetNodeForMouseMoveEvents = static_cast<Element*>(m_lastFatFingersResult.positionWasAdjusted() ? elementUnderFatFinger : m_lastFatFingersResult.node()); + m_convertTouchToMouse = shouldConvertTouchToMouse(possibleTargetNodeForMouseMoveEvents); + + if (elementUnderFatFinger) + drawTapHighlight(); + + // Lets be conservative here: since we have problems on major website having + // mousemove listener for no good reason (e.g. google.com, desktop edition), + // let only delay client notifications when there is not input text node involved. + if (m_convertTouchToMouse + && (m_webPage->m_inputHandler->isInputMode() && !m_lastFatFingersResult.isTextInput())) { + m_webPage->m_inputHandler->setDelayKeyboardVisibilityChange(true); + handleFatFingerPressed(); + } else if (!shouldSuppressMouseDownOnTouchDown()) + handleFatFingerPressed(); + + return true; + } + case Platform::TouchPoint::TouchReleased: + { + // Apply any suppressed changes. This does not eliminate the need + // for the show after the handling of fat finger pressed as it may + // have triggered a state change. + m_webPage->m_inputHandler->processPendingKeyboardVisibilityChange(); + + if (shouldSuppressMouseDownOnTouchDown()) + handleFatFingerPressed(); + + // The rebase has eliminated a necessary event when the mouse does not + // trigger an actual selection change preventing re-showing of the + // keyboard. If input mode is active, call showVirtualKeyboard which + // will update the state and display keyboard if needed. + if (m_webPage->m_inputHandler->isInputMode()) + m_webPage->m_inputHandler->notifyClientOfKeyboardVisibilityChange(true); + + IntPoint adjustedPoint; + if (m_convertTouchToMouse) { + adjustedPoint = point.m_pos; + m_convertTouchToMouse = false; + } else // Fat finger point in viewport coordinates. + adjustedPoint = m_webPage->mapFromContentsToViewport(m_lastFatFingersResult.adjustedPosition()); + + PlatformMouseEvent mouseEvent(adjustedPoint, m_lastScreenPoint, MouseEventReleased, 1, LeftButton, TouchScreen); + m_webPage->handleMouseEvent(mouseEvent); + m_lastFatFingersResult.reset(); // Reset the fat finger result as its no longer valid when a user's finger is not on the screen. + + unsigned spellLength = spellCheck(point); + if (spellLength) { + unsigned end = m_webPage->m_inputHandler->caretPosition(); + unsigned start = end - spellLength; + m_webPage->m_client->requestSpellingSuggestionsForString(start, end); + } + return true; + } + case Platform::TouchPoint::TouchMoved: + if (m_convertTouchToMouse) { + PlatformMouseEvent mouseEvent(point.m_pos, m_lastScreenPoint, MouseEventMoved, 1, LeftButton, TouchScreen); + m_lastScreenPoint = point.m_screenPos; + if (!m_webPage->handleMouseEvent(mouseEvent)) { + m_convertTouchToMouse = false; + return false; + } + return true; + } + break; + default: + break; + } + return false; +} + +unsigned TouchEventHandler::spellCheck(Platform::TouchPoint& touchPoint) +{ + Element* elementUnderFatFinger = m_lastFatFingersResult.nodeAsElementIfApplicable(); + if (!m_lastFatFingersResult.isTextInput() || !elementUnderFatFinger) + return 0; + + IntPoint contentPos(m_webPage->mapFromViewportToContents(touchPoint.m_pos)); + contentPos = DOMSupport::convertPointToFrame(m_webPage->mainFrame(), m_webPage->focusedOrMainFrame(), contentPos); + + Document* document = elementUnderFatFinger->document(); + ASSERT(document); + RenderedDocumentMarker* marker = document->markers()->renderedMarkerContainingPoint(contentPos, DocumentMarker::Spelling); + if (!marker) + return 0; + + IntRect rect = marker->renderedRect(); + IntPoint newContentPos = IntPoint(rect.x() + rect.width(), rect.y() + rect.height() / 2); + Frame* frame = m_webPage->focusedOrMainFrame(); + if (frame != m_webPage->mainFrame()) + newContentPos = m_webPage->mainFrame()->view()->windowToContents(frame->view()->contentsToWindow(newContentPos)); + m_lastFatFingersResult.m_adjustedPosition = newContentPos; + m_lastFatFingersResult.m_positionWasAdjusted = true; + return marker->endOffset() - marker->startOffset(); +} + +void TouchEventHandler::handleFatFingerPressed() +{ + if (!m_didCancelTouch) { + + // First update the mouse position with a MouseMoved event. + PlatformMouseEvent mouseMoveEvent(m_webPage->mapFromContentsToViewport(m_lastFatFingersResult.adjustedPosition()), m_lastScreenPoint, MouseEventMoved, 0, LeftButton, TouchScreen); + m_webPage->handleMouseEvent(mouseMoveEvent); + + // Then send the MousePressed event. + PlatformMouseEvent mousePressedEvent(m_webPage->mapFromContentsToViewport(m_lastFatFingersResult.adjustedPosition()), m_lastScreenPoint, MouseEventPressed, 1, LeftButton, TouchScreen); + m_webPage->handleMouseEvent(mousePressedEvent); + } +} + +// This method filters what element will get tap-highlight'ed or not. To start with, +// we are going to highlight links (anchors with a valid href element), and elements +// whose tap highlight color value is different than the default value. +static Element* elementForTapHighlight(Element* elementUnderFatFinger) +{ + // Do not bail out right way here if there element does not have a renderer. + // It is the casefor <map> (descendent of <area>) elements. The associated <image> + // element actually has the renderer. + if (elementUnderFatFinger->renderer()) { + Color tapHighlightColor = elementUnderFatFinger->renderStyle()->tapHighlightColor(); + if (tapHighlightColor != RenderTheme::defaultTheme()->platformTapHighlightColor()) + return elementUnderFatFinger; + } + + bool isArea = elementUnderFatFinger->hasTagName(HTMLNames::areaTag); + Node* linkNode = elementUnderFatFinger->enclosingLinkEventParentOrSelf(); + if (!linkNode || !linkNode->isHTMLElement() || (!linkNode->renderer() && !isArea)) + return 0; + + ASSERT(linkNode->isLink()); + + // FatFingers class selector ensure only anchor with valid href attr value get here. + // It includes empty hrefs. + Element* highlightCandidateElement = static_cast<Element*>(linkNode); + + if (!isArea) + return highlightCandidateElement; + + HTMLAreaElement* area = static_cast<HTMLAreaElement*>(highlightCandidateElement); + HTMLImageElement* image = area->imageElement(); + if (image && image->renderer()) + return image; + + return 0; +} + +void TouchEventHandler::drawTapHighlight() +{ + Element* elementUnderFatFinger = m_lastFatFingersResult.nodeAsElementIfApplicable(); + if (!elementUnderFatFinger) + return; + + Element* element = elementForTapHighlight(elementUnderFatFinger); + if (!element) + return; + + // Get the element bounding rect in transformed coordinates so we can extract + // the focus ring relative position each rect. + RenderObject* renderer = element->renderer(); + ASSERT(renderer); + + Frame* elementFrame = element->document()->frame(); + ASSERT(elementFrame); + + FrameView* elementFrameView = elementFrame->view(); + if (!elementFrameView) + return; + + // Tell the client if the element is either in a scrollable container or in a fixed positioned container. + // On the client side, this info is being used to hide the tap highlight window on scroll. + RenderLayer* layer = m_webPage->enclosingFixedPositionedAncestorOrSelfIfFixedPositioned(renderer->enclosingLayer()); + bool shouldHideTapHighlightRightAfterScrolling = !layer->renderer()->isRenderView(); + shouldHideTapHighlightRightAfterScrolling |= !!m_webPage->m_inRegionScrollStartingNode.get(); + + IntPoint framePos(m_webPage->frameOffset(elementFrame)); + + // FIXME: We can get more precise on the <map> case by calculating the rect with HTMLAreaElement::computeRect(). + IntRect absoluteRect = renderer->absoluteClippedOverflowRect(); + absoluteRect.move(framePos.x(), framePos.y()); + + IntRect clippingRect; + if (elementFrame == m_webPage->mainFrame()) + clippingRect = IntRect(IntPoint(0, 0), elementFrameView->contentsSize()); + else + clippingRect = m_webPage->mainFrame()->view()->windowToContents(m_webPage->getRecursiveVisibleWindowRect(elementFrameView, true /*noClipToMainFrame*/)); + clippingRect = intersection(absoluteRect, clippingRect); + + Vector<FloatQuad> focusRingQuads; + renderer->absoluteFocusRingQuads(focusRingQuads); + + Platform::IntRectRegion region; + for (size_t i = 0; i < focusRingQuads.size(); ++i) { + IntRect rect = focusRingQuads[i].enclosingBoundingBox(); + rect.move(framePos.x(), framePos.y()); + IntRect clippedRect = intersection(clippingRect, rect); + clippedRect.inflate(2); + region = unionRegions(region, Platform::IntRect(clippedRect)); + } + + Color highlightColor = element->renderStyle()->tapHighlightColor(); + + m_webPage->m_client->drawTapHighlight(region, + highlightColor.red(), + highlightColor.green(), + highlightColor.blue(), + highlightColor.alpha(), + shouldHideTapHighlightRightAfterScrolling); +} + +} +} |