2012-02-16 Antonio Gomes <agomes@rim.com>
authortonikitoo@webkit.org <tonikitoo@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 16 Feb 2012 22:03:33 +0000 (22:03 +0000)
committertonikitoo@webkit.org <tonikitoo@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 16 Feb 2012 22:03:33 +0000 (22:03 +0000)
        [BlackBerry] Upstream touch handling related classes (part II: FatFingers)
        https://bugs.webkit.org/show_bug.cgi?id=78509

        Reviewed by Rob Buis.

        FatFingers is the class responsible to the whole touch
        accuracy of the BlackBerry port. Initial upstream.

        * WebKitSupport/FatFingers.cpp: Added.
        (WebKit):
        (BlackBerry::WebKit::FatFingers::fingerRectForPoint):
        (BlackBerry::WebKit::hasMousePressListener):
        (BlackBerry::WebKit::FatFingers::isElementClickable):
        (BlackBerry::WebKit::isFieldWithText):
        (BlackBerry::WebKit::distanceBetweenPoints):
        (BlackBerry::WebKit::compareDistanceBetweenPoints):
        (BlackBerry::WebKit::isValidFrameOwner):
        (BlackBerry::WebKit::FatFingers::FatFingers):
        (BlackBerry::WebKit::FatFingers::~FatFingers):
        (BlackBerry::WebKit::FatFingers::findBestPoint):
        (BlackBerry::WebKit::FatFingers::checkFingerIntersection):
        (BlackBerry::WebKit::FatFingers::findIntersectingRegions):
        (BlackBerry::WebKit::FatFingers::checkForClickableElement):
        (BlackBerry::WebKit::FatFingers::checkForText):
        (BlackBerry::WebKit::FatFingers::getPaddings):
        (BlackBerry::WebKit::FatFingers::nodesFromRect):
        (BlackBerry::WebKit::FatFingers::getRelevantInfoFromPoint):
        (BlackBerry::WebKit::FatFingers::setSuccessfulFatFingersResult):
        * WebKitSupport/FatFingers.h: Added.
        (WebCore):
        (WebKit):
        (FatFingersResult):
        (BlackBerry::WebKit::FatFingersResult::FatFingersResult):
        (BlackBerry::WebKit::FatFingersResult::reset):
        (BlackBerry::WebKit::FatFingersResult::originPosition):
        (BlackBerry::WebKit::FatFingersResult::adjustedPosition):
        (BlackBerry::WebKit::FatFingersResult::positionWasAdjusted):
        (BlackBerry::WebKit::FatFingersResult::isTextInput):
        (BlackBerry::WebKit::FatFingersResult::isValid):
        (BlackBerry::WebKit::FatFingersResult::validNode):
        (BlackBerry::WebKit::FatFingersResult::nodeAsElementIfApplicable):
        (FatFingers):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@107978 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebKit/blackberry/ChangeLog
Source/WebKit/blackberry/WebKitSupport/FatFingers.cpp [new file with mode: 0644]
Source/WebKit/blackberry/WebKitSupport/FatFingers.h [new file with mode: 0644]

index cc66f7f..7503dd7 100644 (file)
@@ -5,6 +5,51 @@
 
         Reviewed by Rob Buis.
 
+        FatFingers is the class responsible to the whole touch
+        accuracy of the BlackBerry port. Initial upstream.
+
+        * WebKitSupport/FatFingers.cpp: Added.
+        (WebKit):
+        (BlackBerry::WebKit::FatFingers::fingerRectForPoint):
+        (BlackBerry::WebKit::hasMousePressListener):
+        (BlackBerry::WebKit::FatFingers::isElementClickable):
+        (BlackBerry::WebKit::isFieldWithText):
+        (BlackBerry::WebKit::distanceBetweenPoints):
+        (BlackBerry::WebKit::compareDistanceBetweenPoints):
+        (BlackBerry::WebKit::isValidFrameOwner):
+        (BlackBerry::WebKit::FatFingers::FatFingers):
+        (BlackBerry::WebKit::FatFingers::~FatFingers):
+        (BlackBerry::WebKit::FatFingers::findBestPoint):
+        (BlackBerry::WebKit::FatFingers::checkFingerIntersection):
+        (BlackBerry::WebKit::FatFingers::findIntersectingRegions):
+        (BlackBerry::WebKit::FatFingers::checkForClickableElement):
+        (BlackBerry::WebKit::FatFingers::checkForText):
+        (BlackBerry::WebKit::FatFingers::getPaddings):
+        (BlackBerry::WebKit::FatFingers::nodesFromRect):
+        (BlackBerry::WebKit::FatFingers::getRelevantInfoFromPoint):
+        (BlackBerry::WebKit::FatFingers::setSuccessfulFatFingersResult):
+        * WebKitSupport/FatFingers.h: Added.
+        (WebCore):
+        (WebKit):
+        (FatFingersResult):
+        (BlackBerry::WebKit::FatFingersResult::FatFingersResult):
+        (BlackBerry::WebKit::FatFingersResult::reset):
+        (BlackBerry::WebKit::FatFingersResult::originPosition):
+        (BlackBerry::WebKit::FatFingersResult::adjustedPosition):
+        (BlackBerry::WebKit::FatFingersResult::positionWasAdjusted):
+        (BlackBerry::WebKit::FatFingersResult::isTextInput):
+        (BlackBerry::WebKit::FatFingersResult::isValid):
+        (BlackBerry::WebKit::FatFingersResult::validNode):
+        (BlackBerry::WebKit::FatFingersResult::nodeAsElementIfApplicable):
+        (FatFingers):
+
+2012-02-16  Antonio Gomes  <agomes@rim.com>
+
+        [BlackBerry] Upstream touch handling related classes
+        https://bugs.webkit.org/show_bug.cgi?id=78509
+
+        Reviewed by Rob Buis.
+
         Initial upstream of the Blackberry specific single touch event
         handler class.
 
diff --git a/Source/WebKit/blackberry/WebKitSupport/FatFingers.cpp b/Source/WebKit/blackberry/WebKitSupport/FatFingers.cpp
new file mode 100644 (file)
index 0000000..2bbf3ee
--- /dev/null
@@ -0,0 +1,504 @@
+/*
+ * 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 "FatFingers.h"
+
+#include "BlackBerryPlatformLog.h"
+#include "BlackBerryPlatformScreen.h"
+#include "BlackBerryPlatformSettings.h"
+#include "CSSComputedStyleDeclaration.h"
+#include "CSSParser.h"
+#include "DOMSupport.h"
+#include "Document.h"
+#include "Element.h"
+#include "EventNames.h"
+#include "ExceptionCode.h"
+#include "FloatQuad.h"
+#include "Frame.h"
+#include "FrameView.h"
+#include "HTMLFrameOwnerElement.h"
+#include "HTMLInputElement.h"
+#include "HTMLNames.h"
+#include "HTMLTextAreaElement.h"
+#include "Range.h"
+#include "RenderObject.h"
+#include "RenderView.h"
+#include "Text.h"
+#include "TextBreakIterator.h"
+#include "WebPage_p.h"
+
+#if DEBUG_FAT_FINGERS
+#include "BackingStore.h"
+#endif
+
+using BlackBerry::Platform::LogLevelInfo;
+using BlackBerry::Platform::log;
+using BlackBerry::Platform::IntRectRegion;
+using WTF::RefPtr;
+
+using namespace WebCore;
+
+// Lets make the top padding bigger than other directions, since it gets us more
+// accurate clicking results.
+
+namespace BlackBerry {
+namespace WebKit {
+
+#if DEBUG_FAT_FINGERS
+IntRect FatFingers::m_debugFatFingerRect;
+IntPoint FatFingers::m_debugFatFingerClickPosition;
+IntPoint FatFingers::m_debugFatFingerAdjustedPosition;
+#endif
+
+IntRect FatFingers::fingerRectForPoint(const IntPoint& point) const
+{
+    unsigned topPadding, rightPadding, bottomPadding, leftPadding;
+    getPaddings(topPadding, rightPadding, bottomPadding, leftPadding);
+
+    return HitTestResult::rectForPoint(point, topPadding, rightPadding, bottomPadding, leftPadding);
+}
+
+static bool hasMousePressListener(Element* element)
+{
+    ASSERT(element);
+    return element->hasEventListeners(eventNames().clickEvent)
+        || element->hasEventListeners(eventNames().mousedownEvent)
+        || element->hasEventListeners(eventNames().mouseupEvent);
+}
+
+bool FatFingers::isElementClickable(Element* element) const
+{
+    ASSERT(element);
+    ASSERT(m_matchingApproach != Done);
+    ASSERT(m_targetType == ClickableElement);
+
+    switch (m_matchingApproach) {
+    case ClickableByDefault: {
+        ExceptionCode ec = 0;
+        return element->webkitMatchesSelector("a[href],*:link,*:visited,*[role=button],button,input,select,label[for],area[href],textarea,embed,object", ec)
+            || element->isContentEditable();
+    }
+    case MadeClickableByTheWebpage:
+        // FIXME: We fall back to checking for the presence of CSS style "cursor: pointer" to indicate whether the element A
+        // can be clicked when A neither registers mouse events handlers nor is a hyperlink or form control. This workaround
+        // ensures that we don't break various Google web apps, including <http://maps.google.com>. Ideally, we should walk
+        // up the DOM hierarchy to determine the first parent element that accepts mouse events.
+        // Consider the HTML snippet: <div id="A" onclick="..."><div id="B">Example</div></div>
+        // Notice, B is not a hyperlink, or form control, and does not register any mouse event handler. Then B cannot
+        // be clicked. Suppose B specified the CSS property "cursor: pointer". Then, B will be considered as clickable.
+        return hasMousePressListener(element)
+            || computedStyle(element)->getPropertyValue(cssPropertyID("cursor")) == "pointer";
+    default:
+        ASSERT_NOT_REACHED();
+    }
+
+    return false;
+}
+
+// FIXME: Handle content editable nodes here too.
+static inline bool isFieldWithText(Node* node)
+{
+    ASSERT(node);
+    if (!node || !node->isElementNode())
+        return false;
+
+    Element* element = toElement(node);
+    return !DOMSupport::inputElementText(element).isEmpty();
+}
+
+static inline int distanceBetweenPoints(const IntPoint& p1, const IntPoint& p2)
+{
+    int dx = p1.x() - p2.x();
+    int dy = p1.y() - p2.y();
+    return sqrt((double)((dx * dx) + (dy * dy)));
+}
+
+static bool compareDistanceBetweenPoints(const Platform::IntPoint& p, const Platform::IntRectRegion& r1, const Platform::IntRectRegion& r2)
+{
+    return distanceBetweenPoints(p, r1.extents().center()) > distanceBetweenPoints(p, r2.extents().center());
+}
+
+static bool isValidFrameOwner(WebCore::Element* element)
+{
+    ASSERT(element);
+    return element->isFrameOwnerElement() && static_cast<HTMLFrameOwnerElement*>(element)->contentFrame();
+}
+
+// NOTE: 'contentPos' is in main frame contents coordinates.
+FatFingers::FatFingers(WebPagePrivate* webPage, const WebCore::IntPoint& contentPos, TargetType targetType)
+    : m_webPage(webPage)
+    , m_contentPos(contentPos)
+    , m_targetType(targetType)
+    , m_matchingApproach(Done)
+{
+    ASSERT(webPage);
+
+#if DEBUG_FAT_FINGERS
+    m_debugFatFingerRect = IntRect(0, 0, 0, 0);
+    m_debugFatFingerClickPosition = m_webPage->mapToTransformed(m_webPage->mapFromContentsToViewport(contentPos));
+    m_debugFatFingerAdjustedPosition = m_webPage->mapToTransformed(m_webPage->mapFromContentsToViewport(contentPos));
+#endif
+}
+
+FatFingers::~FatFingers()
+{
+}
+
+const FatFingersResult FatFingers::findBestPoint()
+{
+    ASSERT(m_webPage);
+
+    if (!m_webPage->m_mainFrame)
+        return m_contentPos;
+    FatFingersResult result(m_contentPos);
+    m_matchingApproach = ClickableByDefault;
+
+    // Lets set nodeUnderFatFinger to the result of a point based hit test here. If something
+    // targable is actually found by ::findIntersectingRegions, then we might replace what we just set below later on.
+    Element* elementUnderPoint;
+    Element* clickableElementUnderPoint;
+    getRelevantInfoFromPoint(m_webPage->m_mainFrame->document(), m_contentPos, elementUnderPoint, clickableElementUnderPoint);
+
+    if (elementUnderPoint) {
+        result.m_nodeUnderFatFinger = elementUnderPoint;
+
+        // If we are looking for a Clickable Element and we found one, we can quit early.
+        if (m_targetType == ClickableElement) {
+            if (clickableElementUnderPoint) {
+                setSuccessfulFatFingersResult(result, clickableElementUnderPoint, m_contentPos /*adjustedPosition*/);
+                return result;
+            }
+
+            if (isElementClickable(elementUnderPoint)) {
+                setSuccessfulFatFingersResult(result, elementUnderPoint, m_contentPos /*adjustedPosition*/);
+                return result;
+            }
+        }
+    }
+
+#if DEBUG_FAT_FINGERS
+    // Force blit to make the fat fingers rects show up.
+    if (!m_debugFatFingerRect.isEmpty())
+        m_webPage->m_backingStore->repaint(0, 0, m_webPage->transformedViewportSize().width(), m_webPage->transformedViewportSize().height(), true, true);
+#endif
+
+    Vector<IntersectingRegion> intersectingRegions;
+    Platform::IntRectRegion remainingFingerRegion = Platform::IntRectRegion(fingerRectForPoint(m_contentPos));
+
+    bool foundOne = findIntersectingRegions(m_webPage->m_mainFrame->document(), intersectingRegions, remainingFingerRegion);
+    if (!foundOne) {
+        m_matchingApproach = MadeClickableByTheWebpage;
+        remainingFingerRegion = Platform::IntRectRegion(fingerRectForPoint(m_contentPos));
+        foundOne = findIntersectingRegions(m_webPage->m_mainFrame->document(), intersectingRegions, remainingFingerRegion);
+    }
+
+    m_matchingApproach = Done;
+
+    if (!foundOne)
+        return result;
+
+    Node* bestNode = 0;
+    Platform::IntRectRegion largestIntersectionRegion;
+    IntPoint bestPoint;
+    int largestIntersectionRegionArea = 0;
+
+    Vector<IntersectingRegion>::const_iterator endIt = intersectingRegions.end();
+    for (Vector<IntersectingRegion>::const_iterator it = intersectingRegions.begin(); it != endIt; ++it) {
+        Node* currentNode = it->first;
+        Platform::IntRectRegion currentIntersectionRegion = it->second;
+
+        int currentIntersectionRegionArea = currentIntersectionRegion.area();
+        if (currentIntersectionRegionArea > largestIntersectionRegionArea
+            || (currentIntersectionRegionArea == largestIntersectionRegionArea
+            && compareDistanceBetweenPoints(m_contentPos, currentIntersectionRegion, largestIntersectionRegion))) {
+            bestNode = currentNode;
+            largestIntersectionRegion = currentIntersectionRegion;
+            largestIntersectionRegionArea = currentIntersectionRegionArea;
+        }
+    }
+
+    if (!bestNode || largestIntersectionRegion.isEmpty())
+        return result;
+
+#if DEBUG_FAT_FINGERS
+    m_debugFatFingerAdjustedPosition = m_webPage->mapToTransformed(m_webPage->mapFromContentsToViewport(largestIntersectionRegion.rects()[0].center()));
+#endif
+
+    setSuccessfulFatFingersResult(result, bestNode, largestIntersectionRegion.rects()[0].center() /*adjustedPosition*/);
+
+    return result;
+}
+
+// 'region' is in contents coordinates relative to the frame containing 'node'
+// 'remainingFingerRegion' and 'intersectingRegions' will always be in main frame contents
+// coordinates.
+// Thus, before comparing, we need to map the former to main frame contents coordinates.
+bool FatFingers::checkFingerIntersection(const Platform::IntRectRegion& region,
+                                         const Platform::IntRectRegion& remainingFingerRegion,
+                                         Node* node, Vector<IntersectingRegion>& intersectingRegions)
+{
+    ASSERT(node);
+
+    Platform::IntRectRegion regionCopy(region);
+    WebCore::IntPoint framePos(m_webPage->frameOffset(node->document()->frame()));
+    regionCopy.move(framePos.x(), framePos.y());
+
+    Platform::IntRectRegion intersection = intersectRegions(regionCopy, remainingFingerRegion);
+    if (intersection.isEmpty())
+        return false;
+
+#if DEBUG_FAT_FINGERS
+    String nodeName;
+    if (node->isTextNode())
+        nodeName = "text node";
+    else if (node->isElementNode())
+        nodeName = String::format("%s node", toElement(node)->tagName().latin1().data());
+    else
+        nodeName = "unknown node";
+    log(LogLevelInfo, "%s has region %s, intersecting at %s (area %d)", nodeName.latin1().data(),
+        regionCopy.toString().c_str(), intersection.toString().c_str(), intersection.area());
+#endif
+
+    intersectingRegions.append(std::make_pair(node, intersection));
+    return true;
+}
+
+
+// intersectingRegions and remainingFingerRegion are all in main frame contents coordinates,
+// even on recursive calls of ::findIntersectingRegions.
+bool FatFingers::findIntersectingRegions(Document* document,
+                                         Vector<IntersectingRegion>& intersectingRegions, Platform::IntRectRegion& remainingFingerRegion)
+{
+    if (!document || !document->frame()->view())
+        return false;
+
+    // The layout needs to be up-to-date to determine if a node is focusable.
+    document->updateLayoutIgnorePendingStylesheets();
+
+    // Create fingerRect.
+    IntPoint frameContentPos(document->frame()->view()->windowToContents(m_webPage->m_mainFrame->view()->contentsToWindow(m_contentPos)));
+
+#if DEBUG_FAT_FINGERS
+    IntRect fingerRect(fingerRectForPoint(frameContentPos));
+    IntRect screenFingerRect = m_webPage->mapToTransformed(fingerRect);
+    log(LogLevelInfo, "fat finger rect now %s", screenFingerRect.toString().latin1().data());
+
+    // only record the first finger rect
+    if (document == m_webPage->m_mainFrame->document())
+        m_debugFatFingerRect = m_webPage->mapToTransformed(m_webPage->mapFromContentsToViewport(fingerRect));
+#endif
+
+    // Document::nodesFromRect needs viewport coordinates, but FatFinger::nodesFromRect gets contents coordinates
+    // for simplicity instead.
+    bool foundOne = false;
+    HitTestResult result = nodesFromRect(document, frameContentPos);
+
+    RenderLayer* lowestPositionedEnclosingLayerSoFar = 0;
+
+    // Iterate over the list of nodes (and subrects of nodes where possible), for each saving the
+    // intersection of the bounding box with the finger rect.
+    ListHashSet<RefPtr<Node> > intersectedNodes = result.rectBasedTestResult();
+    ListHashSet<RefPtr<Node> >::const_iterator it = intersectedNodes.begin();
+    ListHashSet<RefPtr<Node> >::const_iterator end = intersectedNodes.end();
+    for ( ; it != end; ++it) {
+        Node* curNode = (*it).get();
+        if (!curNode || !curNode->renderer())
+            continue;
+
+        if (remainingFingerRegion.isEmpty())
+            break;
+
+        bool isElement = curNode->isElementNode();
+        if (isElement && isValidFrameOwner(toElement(curNode))) {
+
+            HTMLFrameOwnerElement* owner = static_cast<HTMLFrameOwnerElement*>(curNode);
+            Document* childDocument = owner && owner->contentFrame() ? owner->contentFrame()->document() : 0;
+            if (!childDocument)
+                continue;
+
+            ASSERT(childDocument->frame()->view());
+
+            foundOne |= findIntersectingRegions(childDocument, intersectingRegions, remainingFingerRegion);
+        } else if (isElement && m_targetType == ClickableElement) {
+            foundOne |= checkForClickableElement(toElement(curNode), intersectingRegions, remainingFingerRegion, lowestPositionedEnclosingLayerSoFar);
+        } else if (m_targetType == Text)
+            foundOne |= checkForText(curNode, intersectingRegions, remainingFingerRegion);
+    }
+
+    return foundOne;
+}
+
+bool FatFingers::checkForClickableElement(Element* curElement,
+                                          Vector<IntersectingRegion>& intersectingRegions,
+                                          Platform::IntRectRegion& remainingFingerRegion,
+                                          RenderLayer*& lowestPositionedEnclosingLayerSoFar)
+{
+    ASSERT(curElement);
+
+    bool intersects = false;
+    Platform::IntRectRegion elementRegion;
+
+    bool isClickableElement = isElementClickable(curElement);
+    if (isClickableElement) {
+        if (curElement->isLink()) {
+            // Links can wrap lines, and in such cases Node::getRect() can give us
+            // not accurate rects, since it unites all InlineBox's rects. In these
+            // cases, we can process each line of the link separately with our
+            // intersection rect, getting a more accurate clicking.
+            Vector<FloatQuad> quads;
+            curElement->renderer()->absoluteFocusRingQuads(quads);
+
+            size_t n = quads.size();
+            ASSERT(n);
+
+            for (size_t i = 0; i < n; ++i)
+                elementRegion = unionRegions(elementRegion, Platform::IntRect(quads[i].enclosingBoundingBox()));
+        } else
+            elementRegion = Platform::IntRectRegion(curElement->renderer()->absoluteBoundingBoxRect(true /*use transforms*/));
+
+    } else
+        elementRegion = Platform::IntRectRegion(curElement->renderer()->absoluteBoundingBoxRect(true /*use transforms*/));
+
+    if (lowestPositionedEnclosingLayerSoFar) {
+        RenderLayer* curElementRenderLayer = m_webPage->enclosingPositionedAncestorOrSelfIfPositioned(curElement->renderer()->enclosingLayer());
+        if (curElementRenderLayer != lowestPositionedEnclosingLayerSoFar) {
+
+            // elementRegion will always be in contents coordinates of its container frame. It needs to be
+            // mapped to main frame contents coordinates in order to subtract the fingerRegion, then.
+            WebCore::IntPoint framePos(m_webPage->frameOffset(curElement->document()->frame()));
+            Platform::IntRectRegion layerRegion(Platform::IntRect(lowestPositionedEnclosingLayerSoFar->renderer()->absoluteBoundingBoxRect(true/*use transforms*/)));
+            layerRegion.move(framePos.x(), framePos.y());
+
+            remainingFingerRegion = subtractRegions(remainingFingerRegion, layerRegion);
+
+            lowestPositionedEnclosingLayerSoFar = curElementRenderLayer;
+        }
+    } else
+        lowestPositionedEnclosingLayerSoFar = m_webPage->enclosingPositionedAncestorOrSelfIfPositioned(curElement->renderer()->enclosingLayer());
+
+    if (isClickableElement)
+        intersects = checkFingerIntersection(elementRegion, remainingFingerRegion, curElement, intersectingRegions);
+
+    return intersects;
+}
+
+bool FatFingers::checkForText(Node* curNode, Vector<IntersectingRegion>& intersectingRegions, Platform::IntRectRegion& fingerRegion)
+{
+    ASSERT(curNode);
+    if (isFieldWithText(curNode)) {
+        // FIXME: Find all text in the field and find the best word.
+        // For now, we will just select the whole field.
+        IntRect boundingRect = curNode->renderer()->absoluteBoundingBoxRect(true /*use transforms*/);
+        Platform::IntRectRegion nodeRegion(boundingRect);
+        return checkFingerIntersection(nodeRegion, fingerRegion, curNode, intersectingRegions);
+    }
+
+    if (curNode->isTextNode()) {
+        WebCore::Text* curText = static_cast<WebCore::Text*>(curNode);
+        String allText = curText->wholeText();
+
+        // Iterate through all words, breaking at whitespace, to find the bounding box of each word.
+        TextBreakIterator* wordIterator = wordBreakIterator(allText.characters(), allText.length());
+
+        int lastOffset = textBreakFirst(wordIterator);
+        if (lastOffset == -1)
+            return false;
+
+        bool foundOne = false;
+        int offset;
+        Document* document = curNode->document();
+
+        while ((offset = textBreakNext(wordIterator)) != -1) {
+            RefPtr<Range> range = Range::create(document, curText, lastOffset, curText, offset);
+            if (!range->text().stripWhiteSpace().isEmpty()) {
+#if DEBUG_FAT_FINGERS
+                log(LogLevelInfo, "Checking word '%s'", range->text().latin1().data());
+#endif
+                Platform::IntRectRegion rangeRegion(DOMSupport::transformedBoundingBoxForRange(*range));
+                foundOne |= checkFingerIntersection(rangeRegion, fingerRegion, curNode, intersectingRegions);
+            }
+            lastOffset = offset;
+        }
+        return foundOne;
+    }
+    return false;
+}
+
+void FatFingers::getPaddings(unsigned& top, unsigned& right, unsigned& bottom, unsigned& left) const
+{
+    static unsigned topPadding = Platform::Settings::get()->topFatFingerPadding();
+    static unsigned rightPadding = Platform::Settings::get()->rightFatFingerPadding();
+    static unsigned bottomPadding = Platform::Settings::get()->bottomFatFingerPadding();
+    static unsigned leftPadding = Platform::Settings::get()->leftFatFingerPadding();
+
+    double currentScale = m_webPage->currentScale();
+    top = topPadding / currentScale;
+    right = rightPadding / currentScale;
+    bottom = bottomPadding / currentScale;
+    left = leftPadding / currentScale;
+}
+
+HitTestResult FatFingers::nodesFromRect(Document* document, const IntPoint& contentPos) const
+{
+    unsigned topPadding, rightPadding, bottomPadding, leftPadding;
+    getPaddings(topPadding, rightPadding, bottomPadding, leftPadding);
+
+    HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::IgnoreClipping);
+    HitTestResult result(contentPos, topPadding, rightPadding, bottomPadding, leftPadding, HitTestShadowDOM);
+
+    document->renderView()->layer()->hitTest(request, result);
+    return result;
+}
+
+void FatFingers::getRelevantInfoFromPoint(Document* document, const IntPoint& contentPos, Element*& elementUnderPoint, Element*& clickableElementUnderPoint) const
+{
+    elementUnderPoint = 0;
+    clickableElementUnderPoint = 0;
+
+    if (!document || !document->renderer() || !document->frame())
+        return;
+
+    HitTestResult result  = document->frame()->eventHandler()->hitTestResultAtPoint(contentPos, true /*allowShadowContent*/);
+    Node* node = result.innerNode();
+    while (node && !node->isElementNode())
+        node = node->parentNode();
+
+    elementUnderPoint = static_cast<Element*>(node);
+    clickableElementUnderPoint = result.URLElement();
+}
+
+void FatFingers::setSuccessfulFatFingersResult(FatFingersResult& result, Node* bestNode, const WebCore::IntPoint& adjustedPoint)
+{
+    result.m_nodeUnderFatFinger = bestNode;
+    result.m_adjustedPosition = adjustedPoint;
+    result.m_positionWasAdjusted = true;
+    result.m_isValid = true;
+
+    bool isTextInputElement = false;
+    if (m_targetType == ClickableElement) {
+        ASSERT(bestNode->isElementNode());
+        Element* bestElement = static_cast<Element*>(bestNode);
+        isTextInputElement = DOMSupport::isTextInputElement(bestElement);
+    }
+    result.m_isTextInput = isTextInputElement;
+}
+
+}
+}
+
diff --git a/Source/WebKit/blackberry/WebKitSupport/FatFingers.h b/Source/WebKit/blackberry/WebKitSupport/FatFingers.h
new file mode 100644 (file)
index 0000000..c17d7b4
--- /dev/null
@@ -0,0 +1,160 @@
+/*
+ * 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
+ */
+
+#ifndef FatFingers_h
+#define FatFingers_h
+
+#include "HitTestResult.h"
+#include "RenderLayer.h"
+
+#include <BlackBerryPlatformIntRectRegion.h>
+
+#include <utility>
+#include <wtf/Vector.h>
+
+namespace WebCore {
+class Document;
+class Element;
+class IntPoint;
+class IntRect;
+class IntSize;
+class Node;
+}
+
+#define DEBUG_FAT_FINGERS 0
+
+namespace BlackBerry {
+namespace WebKit {
+
+class WebPagePrivate;
+class FatFingers;
+class TouchEventHandler;
+
+class FatFingersResult {
+public:
+    FatFingersResult(const WebCore::IntPoint& p = WebCore::IntPoint::zero())
+        : m_originalPosition(p)
+        , m_adjustedPosition(p)
+        , m_positionWasAdjusted(false)
+        , m_isTextInput(false)
+        , m_isValid(false)
+        , m_nodeUnderFatFinger(0)
+    {
+    }
+
+    void reset()
+    {
+        m_originalPosition = m_adjustedPosition = WebCore::IntPoint::zero();
+        m_positionWasAdjusted = false;
+        m_isTextInput = false;
+        m_isValid = false;
+        m_nodeUnderFatFinger = 0;
+    }
+
+    WebCore::IntPoint originPosition() const { return m_originalPosition; }
+    WebCore::IntPoint adjustedPosition() const { return m_adjustedPosition; }
+    bool positionWasAdjusted() const { return m_isValid && m_positionWasAdjusted; }
+    bool isTextInput() const { return m_isValid && !!m_nodeUnderFatFinger && m_isTextInput; }
+    bool isValid() const { return m_isValid; }
+    WebCore::Node* validNode() const { return m_nodeUnderFatFinger && m_nodeUnderFatFinger->inDocument() ? m_nodeUnderFatFinger.get() : 0; }
+    WebCore::Element* nodeAsElementIfApplicable() const
+    {
+        return static_cast<WebCore::Element*>(m_nodeUnderFatFinger && m_nodeUnderFatFinger->inDocument() && m_nodeUnderFatFinger->isElementNode() ? m_nodeUnderFatFinger.get() : 0);
+    }
+
+private:
+    friend class WebKit::FatFingers;
+    friend class WebKit::TouchEventHandler;
+
+    WebCore::IntPoint m_originalPosition; // Main frame contents coordinates.
+    WebCore::IntPoint m_adjustedPosition; // Main frame contents coordinates.
+    bool m_positionWasAdjusted;
+    bool m_isTextInput; // Check if the element under the touch point will require a VKB be displayed so that
+                        // the touch down can be suppressed.
+    bool m_isValid;
+    RefPtr<WebCore::Node> m_nodeUnderFatFinger;
+};
+
+class FatFingers {
+public:
+    enum TargetType { ClickableElement, Text };
+
+    FatFingers(WebPagePrivate* webpage, const WebCore::IntPoint& contentPos, TargetType);
+    ~FatFingers();
+
+    const FatFingersResult findBestPoint();
+
+#if DEBUG_FAT_FINGERS
+    // These debug vars are all in content coordinates. They are public so
+    // they can be read from BackingStore, which will draw a visible rect
+    // around the fat fingers area.
+    static WebCore::IntRect m_debugFatFingerRect;
+    static WebCore::IntPoint m_debugFatFingerClickPosition;
+    static WebCore::IntPoint m_debugFatFingerAdjustedPosition;
+#endif
+
+private:
+    enum MatchingApproachForClickable { ClickableByDefault = 0, MadeClickableByTheWebpage, Done };
+
+    typedef std::pair<WebCore::Node*, Platform::IntRectRegion> IntersectingRegion;
+
+    bool checkFingerIntersection(const Platform::IntRectRegion&,
+                                 const Platform::IntRectRegion& remainingFingerRegion,
+                                 WebCore::Node*,
+                                 Vector<IntersectingRegion>& intersectingRegions);
+
+    bool findIntersectingRegions(WebCore::Document*,
+                                 Vector<IntersectingRegion>& intersectingRegions,
+                                 Platform::IntRectRegion& remainingFingerRegion);
+
+    bool checkForClickableElement(WebCore::Element*,
+                                  Vector<IntersectingRegion>& intersectingRegions,
+                                  Platform::IntRectRegion& remainingFingerRegion,
+                                  WebCore::RenderLayer*& lowestPositionedEnclosingLayerSoFar);
+
+    bool checkForText(WebCore::Node*,
+                      Vector<IntersectingRegion>& intersectingRegions,
+                      Platform::IntRectRegion& fingerRegion);
+
+    void setSuccessfulFatFingersResult(FatFingersResult&, WebCore::Node*, const WebCore::IntPoint&);
+
+    // It mimics Document::nodesFromRect, but has a different return value to fit our needs.
+    WebCore::HitTestResult nodesFromRect(WebCore::Document*, const WebCore::IntPoint&) const;
+
+    // It mimics Document::elementFromPoint, but recursively hit-tests in case an inner frame is found.
+    void getRelevantInfoFromPoint(WebCore::Document*,
+                                  const WebCore::IntPoint&,
+                                  WebCore::Element*& elementUnderPoint,
+                                  WebCore::Element*& clickableElementUnderPoint) const;
+
+    bool isElementClickable(WebCore::Element*) const;
+
+    inline WebCore::IntRect fingerRectForPoint(const WebCore::IntPoint&) const;
+    void getPaddings(unsigned& top, unsigned& right, unsigned& bottom, unsigned& left) const;
+
+    WebPagePrivate* m_webPage;
+    WebCore::IntPoint m_contentPos;
+    TargetType m_targetType;
+    MatchingApproachForClickable m_matchingApproach;
+};
+
+}
+}
+
+#endif // FatFingers_h
+