summaryrefslogtreecommitdiff
path: root/Source/WebKit/blackberry/WebKitSupport/TouchEventHandler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebKit/blackberry/WebKitSupport/TouchEventHandler.cpp')
-rw-r--r--Source/WebKit/blackberry/WebKitSupport/TouchEventHandler.cpp405
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);
+}
+
+}
+}