[BlackBerry]Adjust fatfinger detection rect size
[WebKit-https.git] / Source / WebKit / blackberry / WebKitSupport / FatFingers.cpp
1 /*
2  * Copyright (C) 2010, 2011, 2012 Research In Motion Limited. All rights reserved.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17  */
18
19 #include "config.h"
20 #include "FatFingers.h"
21
22 #include "BlackBerryPlatformLog.h"
23 #include "BlackBerryPlatformScreen.h"
24 #include "BlackBerryPlatformSettings.h"
25 #include "CSSComputedStyleDeclaration.h"
26 #include "CSSParser.h"
27 #include "DOMSupport.h"
28 #include "Document.h"
29 #include "Element.h"
30 #include "EventNames.h"
31 #include "ExceptionCode.h"
32 #include "FloatQuad.h"
33 #include "Frame.h"
34 #include "FrameView.h"
35 #include "HTMLFrameOwnerElement.h"
36 #include "HTMLInputElement.h"
37 #include "HTMLNames.h"
38 #include "HTMLTextAreaElement.h"
39 #include "Range.h"
40 #include "RenderObject.h"
41 #include "RenderView.h"
42 #include "Text.h"
43 #include "TextBreakIterator.h"
44 #include "WebPage_p.h"
45
46 #if DEBUG_FAT_FINGERS
47 #include "BackingStore.h"
48 #endif
49
50 using BlackBerry::Platform::IntRectRegion;
51
52 using namespace WebCore;
53
54 // Lets make the top padding bigger than other directions, since it gets us more
55 // accurate clicking results.
56
57 namespace BlackBerry {
58 namespace WebKit {
59
60 #if DEBUG_FAT_FINGERS
61 IntRect FatFingers::m_debugFatFingerRect;
62 IntPoint FatFingers::m_debugFatFingerClickPosition;
63 IntPoint FatFingers::m_debugFatFingerAdjustedPosition;
64 #endif
65
66 IntRect FatFingers::fingerRectForPoint(const IntPoint& point) const
67 {
68     unsigned topPadding, rightPadding, bottomPadding, leftPadding;
69     getPaddings(topPadding, rightPadding, bottomPadding, leftPadding);
70
71     return HitTestResult::rectForPoint(point, topPadding, rightPadding, bottomPadding, leftPadding);
72 }
73
74 static bool hasMousePressListener(Element* element)
75 {
76     ASSERT(element);
77     return element->hasEventListeners(eventNames().clickEvent)
78         || element->hasEventListeners(eventNames().mousedownEvent)
79         || element->hasEventListeners(eventNames().mouseupEvent);
80 }
81
82 bool FatFingers::isElementClickable(Element* element) const
83 {
84     ASSERT(element);
85     ASSERT(m_targetType == ClickableElement);
86
87     ExceptionCode ec = 0;
88
89     if (element->webkitMatchesSelector("a[href],*:link,*:visited,*[role=button],button,input,select,label[for],area[href],textarea,embed,object", ec)
90         || element->isMediaControlElement()
91         || element->isContentEditable())
92         return true;
93
94     if (element->isInShadowTree())
95         return false;
96
97     return hasMousePressListener(element)
98         || CSSComputedStyleDeclaration::create(element)->getPropertyValue(cssPropertyID("cursor")) == "pointer";
99 }
100
101 // FIXME: Handle content editable nodes here too.
102 static inline bool isFieldWithText(Node* node)
103 {
104     ASSERT(node);
105     if (!node || !node->isElementNode())
106         return false;
107
108     Element* element = toElement(node);
109     return !DOMSupport::inputElementText(element).isEmpty();
110 }
111
112 static inline int distanceBetweenPoints(const IntPoint& p1, const IntPoint& p2)
113 {
114     int dx = p1.x() - p2.x();
115     int dy = p1.y() - p2.y();
116     return sqrt((double)((dx * dx) + (dy * dy)));
117 }
118
119 static bool compareDistanceBetweenPoints(const Platform::IntPoint& p, const IntRectRegion& r1, const IntRectRegion& r2)
120 {
121     return distanceBetweenPoints(p, r1.extents().center()) > distanceBetweenPoints(p, r2.extents().center());
122 }
123
124 static bool isValidFrameOwner(WebCore::Element* element)
125 {
126     ASSERT(element);
127     return element->isFrameOwnerElement() && static_cast<HTMLFrameOwnerElement*>(element)->contentFrame();
128 }
129
130 // NOTE: 'contentPos' is in main frame contents coordinates.
131 FatFingers::FatFingers(WebPagePrivate* webPage, const WebCore::IntPoint& contentPos, TargetType targetType)
132     : m_webPage(webPage)
133     , m_contentPos(contentPos)
134     , m_targetType(targetType)
135 {
136     ASSERT(webPage);
137
138 #if DEBUG_FAT_FINGERS
139     m_debugFatFingerRect = IntRect(0, 0, 0, 0);
140     m_debugFatFingerClickPosition = m_webPage->mapToTransformed(m_webPage->mapFromContentsToViewport(contentPos));
141     m_debugFatFingerAdjustedPosition = m_webPage->mapToTransformed(m_webPage->mapFromContentsToViewport(contentPos));
142 #endif
143 }
144
145 FatFingers::~FatFingers()
146 {
147 }
148
149 const FatFingersResult FatFingers::findBestPoint()
150 {
151     ASSERT(m_webPage);
152     ASSERT(m_webPage->m_mainFrame);
153
154     m_cachedRectHitTestResults.clear();
155
156     FatFingersResult result(m_contentPos);
157
158     // Lets set nodeUnderFatFinger to the result of a point based hit test here. If something
159     // targable is actually found by ::findIntersectingRegions, then we might replace what we just set below later on.
160     Element* elementUnderPoint;
161     Element* clickableElementUnderPoint;
162     getRelevantInfoFromCachedHitTest(elementUnderPoint, clickableElementUnderPoint);
163
164     if (elementUnderPoint) {
165         result.m_nodeUnderFatFinger = elementUnderPoint;
166
167         // If we are looking for a Clickable Element and we found one, we can quit early.
168         if (m_targetType == ClickableElement) {
169             if (clickableElementUnderPoint) {
170                 setSuccessfulFatFingersResult(result, clickableElementUnderPoint, m_contentPos /*adjustedPosition*/);
171                 return result;
172             }
173
174             if (isElementClickable(elementUnderPoint)) {
175                 setSuccessfulFatFingersResult(result, elementUnderPoint, m_contentPos /*adjustedPosition*/);
176                 return result;
177             }
178         }
179     }
180
181 #if DEBUG_FAT_FINGERS
182     // Force blit to make the fat fingers rects show up.
183     if (!m_debugFatFingerRect.isEmpty())
184         m_webPage->m_backingStore->repaint(0, 0, m_webPage->transformedViewportSize().width(), m_webPage->transformedViewportSize().height(), true, true);
185 #endif
186
187     Vector<IntersectingRegion> intersectingRegions;
188     IntRectRegion remainingFingerRegion = IntRectRegion(fingerRectForPoint(m_contentPos));
189
190     bool foundOne = findIntersectingRegions(m_webPage->m_mainFrame->document(), intersectingRegions, remainingFingerRegion);
191
192     m_cachedRectHitTestResults.clear();
193
194     if (!foundOne)
195         return result;
196
197     Node* bestNode = 0;
198     IntRectRegion largestIntersectionRegion;
199     int largestIntersectionRegionArea = 0;
200
201     Vector<IntersectingRegion>::const_iterator endIt = intersectingRegions.end();
202     for (Vector<IntersectingRegion>::const_iterator it = intersectingRegions.begin(); it != endIt; ++it) {
203         Node* currentNode = it->first;
204         IntRectRegion currentIntersectionRegion = it->second;
205
206         int currentIntersectionRegionArea = currentIntersectionRegion.area();
207         if (currentIntersectionRegionArea > largestIntersectionRegionArea
208             || (currentIntersectionRegionArea == largestIntersectionRegionArea
209             && compareDistanceBetweenPoints(m_contentPos, currentIntersectionRegion, largestIntersectionRegion))) {
210             bestNode = currentNode;
211             largestIntersectionRegion = currentIntersectionRegion;
212             largestIntersectionRegionArea = currentIntersectionRegionArea;
213         }
214     }
215
216     if (!bestNode || largestIntersectionRegion.isEmpty())
217         return result;
218
219 #if DEBUG_FAT_FINGERS
220     m_debugFatFingerAdjustedPosition = m_webPage->mapToTransformed(m_webPage->mapFromContentsToViewport(largestIntersectionRegion.rects()[0].center()));
221 #endif
222
223     setSuccessfulFatFingersResult(result, bestNode, largestIntersectionRegion.rects()[0].center() /*adjustedPosition*/);
224
225     return result;
226 }
227
228 // 'region' is in contents coordinates relative to the frame containing 'node'
229 // 'remainingFingerRegion' and 'intersectingRegions' will always be in main frame contents
230 // coordinates.
231 // Thus, before comparing, we need to map the former to main frame contents coordinates.
232 bool FatFingers::checkFingerIntersection(const IntRectRegion& region, const IntRectRegion& remainingFingerRegion,
233                                          Node* node, Vector<IntersectingRegion>& intersectingRegions)
234 {
235     ASSERT(node);
236
237     IntRectRegion regionCopy(region);
238     WebCore::IntPoint framePos(m_webPage->frameOffset(node->document()->frame()));
239     regionCopy.move(framePos.x(), framePos.y());
240
241     IntRectRegion intersection = intersectRegions(regionCopy, remainingFingerRegion);
242     if (intersection.isEmpty())
243         return false;
244
245 #if DEBUG_FAT_FINGERS
246     String nodeName;
247     if (node->isTextNode())
248         nodeName = "text node";
249     else if (node->isElementNode())
250         nodeName = String::format("%s node", toElement(node)->tagName().latin1().data());
251     else
252         nodeName = "unknown node";
253     if (node->isInShadowTree()) {
254         nodeName = nodeName + "(in shadow tree";
255         if (node->isElementNode() && !toElement(node)->shadowPseudoId().isEmpty())
256             nodeName = nodeName + ", pseudo id " + toElement(node)->shadowPseudoId();
257         nodeName = nodeName + ")";
258     }
259     Platform::logAlways(Platform::LogLevelInfo,
260         "%s has region %s, intersecting at %s (area %d)", nodeName.latin1().data(),
261         regionCopy.toString().c_str(), intersection.toString().c_str(), intersection.area());
262 #endif
263
264     intersectingRegions.append(std::make_pair(node, intersection));
265     return true;
266 }
267
268
269 // intersectingRegions and remainingFingerRegion are all in main frame contents coordinates,
270 // even on recursive calls of ::findIntersectingRegions.
271 bool FatFingers::findIntersectingRegions(Document* document, Vector<IntersectingRegion>& intersectingRegions, IntRectRegion& remainingFingerRegion)
272 {
273     if (!document || !document->frame()->view())
274         return false;
275
276     // The layout needs to be up-to-date to determine if a node is focusable.
277     document->updateLayoutIgnorePendingStylesheets();
278
279     // Create fingerRect.
280     IntPoint frameContentPos(document->frame()->view()->windowToContents(m_webPage->m_mainFrame->view()->contentsToWindow(m_contentPos)));
281     IntRect viewportRect = m_webPage->mainFrame()->view()->visibleContentRect();
282
283     // Ensure the frameContentPos is inside the viewport.
284     frameContentPos = Platform::pointClampedToRect(frameContentPos, viewportRect);
285
286 #if DEBUG_FAT_FINGERS
287     IntRect fingerRect(fingerRectForPoint(frameContentPos));
288     Platform::IntRect screenFingerRect = m_webPage->mapToTransformed(fingerRect);
289     Platform::logAlways(Platform::LogLevelInfo, "fat finger rect now %s", screenFingerRect.toString().c_str());
290
291     // only record the first finger rect
292     if (document == m_webPage->m_mainFrame->document())
293         m_debugFatFingerRect = m_webPage->mapToTransformed(m_webPage->mapFromContentsToViewport(fingerRect));
294 #endif
295
296     bool foundOne = false;
297
298     RenderLayer* lowestPositionedEnclosingLayerSoFar = 0;
299
300     // Iterate over the list of nodes (and subrects of nodes where possible), for each saving the
301     // intersection of the bounding box with the finger rect.
302     ListHashSet<RefPtr<Node> > intersectedNodes;
303     getNodesFromRect(document, frameContentPos, intersectedNodes);
304
305     ListHashSet<RefPtr<Node> >::const_iterator it = intersectedNodes.begin();
306     ListHashSet<RefPtr<Node> >::const_iterator end = intersectedNodes.end();
307     for ( ; it != end; ++it) {
308         Node* curNode = (*it).get();
309         if (!curNode || !curNode->renderer())
310             continue;
311
312         if (remainingFingerRegion.isEmpty())
313             break;
314
315         bool isElement = curNode->isElementNode();
316         if (isElement && isValidFrameOwner(toElement(curNode))) {
317
318             HTMLFrameOwnerElement* owner = static_cast<HTMLFrameOwnerElement*>(curNode);
319             Document* childDocument = owner && owner->contentFrame() ? owner->contentFrame()->document() : 0;
320             if (!childDocument)
321                 continue;
322
323             ASSERT(childDocument->frame()->view());
324
325             foundOne |= findIntersectingRegions(childDocument, intersectingRegions, remainingFingerRegion);
326         } else if (isElement && m_targetType == ClickableElement) {
327             foundOne |= checkForClickableElement(toElement(curNode), intersectingRegions, remainingFingerRegion, lowestPositionedEnclosingLayerSoFar);
328         } else if (m_targetType == Text)
329             foundOne |= checkForText(curNode, intersectingRegions, remainingFingerRegion);
330     }
331
332     return foundOne;
333 }
334
335 bool FatFingers::checkForClickableElement(Element* curElement,
336                                           Vector<IntersectingRegion>& intersectingRegions,
337                                           IntRectRegion& remainingFingerRegion,
338                                           RenderLayer*& lowestPositionedEnclosingLayerSoFar)
339 {
340     ASSERT(curElement);
341
342     bool intersects = false;
343     IntRectRegion elementRegion;
344
345     bool isClickableElement = isElementClickable(curElement);
346     if (isClickableElement) {
347         if (curElement->isLink()) {
348             // Links can wrap lines, and in such cases Node::boundingBox() can give us
349             // not accurate rects, since it unites all InlineBox's rects. In these
350             // cases, we can process each line of the link separately with our
351             // intersection rect, getting a more accurate clicking.
352             Vector<FloatQuad> quads;
353             curElement->renderer()->absoluteFocusRingQuads(quads);
354
355             size_t n = quads.size();
356             ASSERT(n);
357
358             for (size_t i = 0; i < n; ++i)
359                 elementRegion = unionRegions(elementRegion, Platform::IntRect(quads[i].enclosingBoundingBox()));
360         } else
361             elementRegion = IntRectRegion(curElement->renderer()->absoluteBoundingBoxRect(true /*use transforms*/));
362
363     } else
364         elementRegion = IntRectRegion(curElement->renderer()->absoluteBoundingBoxRect(true /*use transforms*/));
365
366     if (lowestPositionedEnclosingLayerSoFar) {
367         RenderLayer* curElementRenderLayer = m_webPage->enclosingPositionedAncestorOrSelfIfPositioned(curElement->renderer()->enclosingLayer());
368         if (curElementRenderLayer != lowestPositionedEnclosingLayerSoFar) {
369
370             // elementRegion will always be in contents coordinates of its container frame. It needs to be
371             // mapped to main frame contents coordinates in order to subtract the fingerRegion, then.
372             WebCore::IntPoint framePos(m_webPage->frameOffset(curElement->document()->frame()));
373             IntRectRegion layerRegion(Platform::IntRect(lowestPositionedEnclosingLayerSoFar->renderer()->absoluteBoundingBoxRect(true/*use transforms*/)));
374             layerRegion.move(framePos.x(), framePos.y());
375
376             remainingFingerRegion = subtractRegions(remainingFingerRegion, layerRegion);
377
378             lowestPositionedEnclosingLayerSoFar = curElementRenderLayer;
379         }
380     } else
381         lowestPositionedEnclosingLayerSoFar = m_webPage->enclosingPositionedAncestorOrSelfIfPositioned(curElement->renderer()->enclosingLayer());
382
383     if (isClickableElement)
384         intersects = checkFingerIntersection(elementRegion, remainingFingerRegion, curElement, intersectingRegions);
385
386     return intersects;
387 }
388
389 bool FatFingers::checkForText(Node* curNode, Vector<IntersectingRegion>& intersectingRegions, IntRectRegion& fingerRegion)
390 {
391     ASSERT(curNode);
392     if (isFieldWithText(curNode)) {
393         // FIXME: Find all text in the field and find the best word.
394         // For now, we will just select the whole field.
395         IntRect boundingRect = curNode->renderer()->absoluteBoundingBoxRect(true /*use transforms*/);
396         IntRectRegion nodeRegion(boundingRect);
397         return checkFingerIntersection(nodeRegion, fingerRegion, curNode, intersectingRegions);
398     }
399
400     if (curNode->isTextNode()) {
401         WebCore::Text* curText = static_cast<WebCore::Text*>(curNode);
402         String allText = curText->wholeText();
403
404         // Iterate through all words, breaking at whitespace, to find the bounding box of each word.
405         TextBreakIterator* wordIterator = wordBreakIterator(allText.characters(), allText.length());
406
407         int lastOffset = textBreakFirst(wordIterator);
408         if (lastOffset == -1)
409             return false;
410
411         bool foundOne = false;
412         int offset;
413         Document* document = curNode->document();
414
415         while ((offset = textBreakNext(wordIterator)) != -1) {
416             RefPtr<Range> range = Range::create(document, curText, lastOffset, curText, offset);
417             if (!range->text().stripWhiteSpace().isEmpty()) {
418 #if DEBUG_FAT_FINGERS
419                 Platform::logAlways(Platform::LogLevelInfo, "Checking word '%s'", range->text().latin1().data());
420 #endif
421                 IntRectRegion rangeRegion(DOMSupport::transformedBoundingBoxForRange(*range));
422                 foundOne |= checkFingerIntersection(rangeRegion, fingerRegion, curNode, intersectingRegions);
423             }
424             lastOffset = offset;
425         }
426         return foundOne;
427     }
428     return false;
429 }
430
431 void FatFingers::getPaddings(unsigned& top, unsigned& right, unsigned& bottom, unsigned& left) const
432 {
433     static unsigned topPadding = Platform::Settings::instance()->topFatFingerPadding();
434     static unsigned rightPadding = Platform::Settings::instance()->rightFatFingerPadding();
435     static unsigned bottomPadding = Platform::Settings::instance()->bottomFatFingerPadding();
436     static unsigned leftPadding = Platform::Settings::instance()->leftFatFingerPadding();
437
438     double currentScale = m_webPage->currentScale();
439     top = topPadding / currentScale;
440     right = rightPadding / currentScale;
441     bottom = bottomPadding / currentScale;
442     left = leftPadding / currentScale;
443 }
444
445 void FatFingers::getNodesFromRect(Document* document, const IntPoint& contentPos, ListHashSet<RefPtr<Node> >& intersectedNodes)
446 {
447     unsigned topPadding, rightPadding, bottomPadding, leftPadding;
448     getPaddings(topPadding, rightPadding, bottomPadding, leftPadding);
449
450     // The user functions checkForText() and findIntersectingRegions() uses the Node.wholeText() to checkFingerIntersection()
451     // not the text in its shadow tree.
452     HitTestRequest::HitTestRequestType requestType = HitTestRequest::ReadOnly | HitTestRequest::Active;
453     if (m_targetType == Text)
454         requestType |= HitTestRequest::AllowShadowContent;
455     HitTestResult result(contentPos, topPadding, rightPadding, bottomPadding, leftPadding);
456
457     document->renderView()->layer()->hitTest(requestType, result);
458     intersectedNodes = result.rectBasedTestResult();
459     m_cachedRectHitTestResults.add(document, intersectedNodes);
460 }
461
462 void FatFingers::getRelevantInfoFromCachedHitTest(Element*& elementUnderPoint, Element*& clickableElementUnderPoint) const
463 {
464     elementUnderPoint = 0;
465     clickableElementUnderPoint = 0;
466
467     const HitTestResult& result = m_webPage->hitTestResult(m_contentPos);
468     Node* node = result.innerNode();
469     while (node && !node->isElementNode())
470         node = node->parentNode();
471
472     elementUnderPoint = static_cast<Element*>(node);
473     clickableElementUnderPoint = result.URLElement();
474 }
475
476 void FatFingers::setSuccessfulFatFingersResult(FatFingersResult& result, Node* bestNode, const WebCore::IntPoint& adjustedPoint)
477 {
478     result.m_nodeUnderFatFinger = bestNode;
479     result.m_adjustedPosition = adjustedPoint;
480     result.m_positionWasAdjusted = true;
481     result.m_isValid = true;
482
483     bool isTextInputElement = false;
484     if (m_targetType == ClickableElement) {
485         ASSERT_WITH_SECURITY_IMPLICATION(bestNode->isElementNode());
486         Element* bestElement = static_cast<Element*>(bestNode);
487         isTextInputElement = DOMSupport::isTextInputElement(bestElement);
488     }
489     result.m_isTextInput = isTextInputElement;
490 }
491
492 }
493 }
494