123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503 |
- /*
- * Copyright (C) 2010, 2011, 2012, 2013 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 "WebKitThreadViewportAccessor.h"
- #include "WebPage_p.h"
- #if DEBUG_FAT_FINGERS
- #include "BackingStore.h"
- #endif
- using BlackBerry::Platform::IntRectRegion;
- 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
- {
- const Platform::ViewportAccessor* viewportAccessor = m_webPage->m_webkitThreadViewportAccessor;
- unsigned topPadding, rightPadding, bottomPadding, leftPadding;
- IntPoint contentViewportPos = viewportAccessor->documentViewportFromContents(point);
- getAdjustedPaddings(contentViewportPos, topPadding, rightPadding, bottomPadding, leftPadding);
- return HitTestLocation::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_targetType == ClickableElement);
- ExceptionCode ec = 0;
- if (element->webkitMatchesSelector("a[href],*:link,*:visited,*[role=button],button,input,select,label[for],area[href],textarea,embed,object", ec)
- || element->isMediaControlElement()
- || element->isContentEditable()
- || (element->hasTagName(HTMLNames::imgTag) && element->parentNode() && element->parentNode()->hasTagName(HTMLNames::aTag)))
- return true;
- return hasMousePressListener(element)
- || CSSComputedStyleDeclaration::create(element)->getPropertyValue(cssPropertyID("cursor")) == "pointer";
- }
- // 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 IntRectRegion& r1, const 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)
- {
- ASSERT(webPage);
- #if DEBUG_FAT_FINGERS
- const Platform::ViewportAccessor* viewportAccessor = m_webPage->m_webkitThreadViewportAccessor;
- m_debugFatFingerRect = IntRect(0, 0, 0, 0);
- m_debugFatFingerClickPosition = viewportAccessor->pixelViewportFromContents(viewportAccessor->roundToPixelFromDocumentContents(WebCore::FloatPoint(contentPos)));
- m_debugFatFingerAdjustedPosition = m_debugFatFingerClickPosition;
- #endif
- }
- FatFingers::~FatFingers()
- {
- }
- const FatFingersResult FatFingers::findBestPoint()
- {
- ASSERT(m_webPage);
- ASSERT(m_webPage->m_mainFrame);
- // Even though we have clamped the point in libwebview to viewport, but there might be a rounding difference for viewport rect.
- // Clamp position to viewport to ensure we are inside viewport.
- IntRect viewportRect = m_webPage->mainFrame()->view()->visibleContentRect();
- m_contentPos = Platform::pointClampedToRect(m_contentPos, viewportRect);
- FatFingersResult result(m_contentPos);
- // 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.
- const HitTestResult& hitResult = m_webPage->hitTestResult(m_contentPos);
- Node* node = hitResult.innerNode();
- while (node && !node->isElementNode())
- node = node->parentNode();
- Element* elementUnderPoint = toElement(node);
- 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 (isElementClickable(elementUnderPoint)) {
- setSuccessfulFatFingersResult(result, elementUnderPoint, m_contentPos /*adjustedPosition*/);
- return result;
- }
- if (hitResult.URLElement()) {
- setSuccessfulFatFingersResult(result, hitResult.URLElement(), 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;
- IntRectRegion remainingFingerRegion = IntRectRegion(fingerRectForPoint(m_contentPos));
- bool foundOne = findIntersectingRegions(m_webPage->m_mainFrame->document(), intersectingRegions, remainingFingerRegion);
- if (!foundOne)
- return result;
- Node* bestNode = 0;
- IntRectRegion largestIntersectionRegion;
- 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;
- 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
- const Platform::ViewportAccessor* viewportAccessor = m_webPage->m_webkitThreadViewportAccessor;
- m_debugFatFingerAdjustedPosition = viewportAccessor->pixelViewportFromContents(
- viewportAccessor->roundToPixelFromDocumentContents(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 IntRectRegion& region, const IntRectRegion& remainingFingerRegion, Node* node, Vector<IntersectingRegion>& intersectingRegions)
- {
- ASSERT(node);
- IntRectRegion regionCopy(region);
- WebCore::IntPoint framePos(m_webPage->frameOffset(node->document()->frame()));
- regionCopy.move(framePos.x(), framePos.y());
- 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";
- if (node->isInShadowTree()) {
- nodeName = nodeName + "(in shadow tree";
- if (node->isElementNode() && !toElement(node)->shadowPseudoId().isEmpty())
- nodeName = nodeName + ", pseudo id " + toElement(node)->shadowPseudoId();
- nodeName = nodeName + ")";
- }
- Platform::logAlways(Platform::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, 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)));
- IntRect viewportRect = m_webPage->mainFrame()->view()->visibleContentRect();
- // Ensure the frameContentPos is inside the viewport.
- frameContentPos = Platform::pointClampedToRect(frameContentPos, viewportRect);
- #if DEBUG_FAT_FINGERS
- const Platform::ViewportAccessor* viewportAccessor = m_webPage->m_webkitThreadViewportAccessor;
- Platform::IntRect fingerRect(fingerRectForPoint(frameContentPos));
- Platform::IntRect screenFingerRect = viewportAccessor->roundToPixelFromDocumentContents(fingerRect);
- Platform::logAlways(Platform::LogLevelInfo, "fat finger rect now %s", screenFingerRect.toString().c_str());
- // only record the first finger rect
- if (document == m_webPage->m_mainFrame->document())
- m_debugFatFingerRect = viewportAccessor->pixelViewportFromContents(screenFingerRect);
- #endif
- bool foundOne = false;
- 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;
- if (m_webPage->m_cachedRectHitTestResults.contains(document))
- intersectedNodes = m_webPage->m_cachedRectHitTestResults.get(document);
- else
- getNodesFromRect(document, frameContentPos, intersectedNodes);
- 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, IntRectRegion& remainingFingerRegion, RenderLayer*& lowestPositionedEnclosingLayerSoFar)
- {
- ASSERT(curElement);
- bool intersects = false;
- IntRectRegion elementRegion;
- bool isClickableElement = isElementClickable(curElement);
- if (isClickableElement) {
- if (curElement->isLink()) {
- // Links can wrap lines, and in such cases Node::boundingBox() 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 = IntRectRegion(curElement->renderer()->absoluteBoundingBoxRect(true /*use transforms*/));
- } else
- elementRegion = 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 intersect the fingerRegion, then.
- WebCore::IntPoint framePos(m_webPage->frameOffset(curElement->document()->frame()));
- IntRectRegion layerRegion(Platform::IntRect(lowestPositionedEnclosingLayerSoFar->renderer()->absoluteBoundingBoxRect(true/*use transforms*/)));
- layerRegion.move(framePos.x(), framePos.y());
- remainingFingerRegion = intersectRegions(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, 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*/);
- 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
- Platform::logAlways(Platform::LogLevelInfo, "Checking word '%s'", range->text().latin1().data());
- #endif
- IntRectRegion rangeRegion(DOMSupport::transformedBoundingBoxForRange(*range));
- foundOne |= checkFingerIntersection(rangeRegion, fingerRegion, curNode, intersectingRegions);
- }
- lastOffset = offset;
- }
- return foundOne;
- }
- return false;
- }
- void FatFingers::getAdjustedPaddings(const IntPoint& contentViewportPos, unsigned& top, unsigned& right, unsigned& bottom, unsigned& left) const
- {
- static unsigned topPadding = Platform::Settings::instance()->topFatFingerPadding();
- static unsigned rightPadding = Platform::Settings::instance()->rightFatFingerPadding();
- static unsigned bottomPadding = Platform::Settings::instance()->bottomFatFingerPadding();
- static unsigned leftPadding = Platform::Settings::instance()->leftFatFingerPadding();
- double currentScale = m_webPage->currentScale();
- top = topPadding / currentScale;
- right = rightPadding / currentScale;
- bottom = bottomPadding / currentScale;
- left = leftPadding / currentScale;
- IntRect viewportRect = m_webPage->mainFrame()->view()->visibleContentRect();
- // We clamp the event position inside the viewport. We should not expand the fat finger rect to the edge again.
- top = std::min(unsigned(std::max(contentViewportPos.y() - 1, 0)), top);
- left = std::min(unsigned(std::max(contentViewportPos.x() - 1, 0)), left);
- bottom = std::min(unsigned(std::max(viewportRect.height() - contentViewportPos.y() - 1, 0)), bottom);
- right = std::min(unsigned(std::max(viewportRect.width() - contentViewportPos.x() - 1, 0)), right);
- }
- void FatFingers::getNodesFromRect(Document* document, const IntPoint& contentPos, ListHashSet<RefPtr<Node> >& intersectedNodes)
- {
- const Platform::ViewportAccessor* viewportAccessor = m_webPage->m_webkitThreadViewportAccessor;
- unsigned topPadding, rightPadding, bottomPadding, leftPadding;
- IntPoint contentViewportPos = viewportAccessor->documentViewportFromContents(m_contentPos);
- // Do not allow fat fingers detect anything not visible(ie outside of the viewport)
- getAdjustedPaddings(contentViewportPos, topPadding, rightPadding, bottomPadding, leftPadding);
- // The user functions checkForText() and findIntersectingRegions() uses the Node.wholeText() to checkFingerIntersection()
- // not the text in its shadow tree.
- HitTestRequest::HitTestRequestType requestType = HitTestRequest::ReadOnly | HitTestRequest::Active;
- if (m_targetType != Text)
- requestType |= HitTestRequest::DisallowShadowContent;
- HitTestResult result(contentPos, topPadding, rightPadding, bottomPadding, leftPadding);
- document->renderView()->layer()->hitTest(requestType, result);
- intersectedNodes = result.rectBasedTestResult();
- m_webPage->m_cachedRectHitTestResults.add(document, intersectedNodes);
- }
- 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_WITH_SECURITY_IMPLICATION(bestNode->isElementNode());
- Element* bestElement = toElement(bestNode);
- isTextInputElement = DOMSupport::isTextInputElement(bestElement);
- }
- result.m_isTextInput = isTextInputElement;
- }
- }
- }
|