SelectionHandler.cpp   [plain text]


/*
 * 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 "SelectionHandler.h"

#include "DOMSupport.h"
#include "Document.h"
#include "Editor.h"
#include "EditorClient.h"
#include "FatFingers.h"
#include "FloatQuad.h"
#include "Frame.h"
#include "FrameSelection.h"
#include "FrameView.h"
#include "HTMLAnchorElement.h"
#include "HTMLAreaElement.h"
#include "HitTestResult.h"
#include "InputHandler.h"
#include "IntRect.h"
#include "Page.h"
#include "RenderPart.h"
#include "TextGranularity.h"
#include "TouchEventHandler.h"
#include "WebPage.h"
#include "WebPageClient.h"
#include "WebPage_p.h"

#include "htmlediting.h"
#include "visible_units.h"

#include <BlackBerryPlatformKeyboardEvent.h>

#include <sys/keycodes.h>

// Note: This generates a lot of logs when dumping rects lists. It will seriously
// impact performance. Do not enable this during performance tests.
#define SHOWDEBUG_SELECTIONHANDLER 0

using namespace BlackBerry::Platform;
using namespace WebCore;

#if SHOWDEBUG_SELECTIONHANDLER
#define DEBUG_SELECTION(severity, format, ...) logAlways(severity, format, ## __VA_ARGS__)
#else
#define DEBUG_SELECTION(severity, format, ...)
#endif // SHOWDEBUG_SELECTIONHANDLER

namespace BlackBerry {
namespace WebKit {

SelectionHandler::SelectionHandler(WebPagePrivate* page)
    : m_webPage(page)
    , m_selectionActive(false)
    , m_caretActive(false)
    , m_lastUpdatedEndPointIsValid(false)
{
}

SelectionHandler::~SelectionHandler()
{
}

void SelectionHandler::cancelSelection()
{
    m_selectionActive = false;
    m_lastSelectionRegion = IntRectRegion();

    DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::cancelSelection");

    if (m_webPage->m_inputHandler->isInputMode())
        m_webPage->m_inputHandler->cancelSelection();
    else
        m_webPage->focusedOrMainFrame()->selection()->clear();
}

WebString SelectionHandler::selectedText() const
{
    return m_webPage->focusedOrMainFrame()->editor()->selectedText();
}

WebCore::IntRect SelectionHandler::clippingRectForVisibleContent() const
{
    // Get the containing content rect for the frame.
    Frame* frame = m_webPage->focusedOrMainFrame();
    WebCore::IntRect clipRect = WebCore::IntRect(WebCore::IntPoint(0, 0), m_webPage->contentsSize());
    if (frame != m_webPage->mainFrame()) {
        clipRect = m_webPage->getRecursiveVisibleWindowRect(frame->view(), true /* no clip to main frame window */);
        clipRect = m_webPage->m_mainFrame->view()->windowToContents(clipRect);
    }

    // Get the input field containing box.
    WebCore::IntRect inputBoundingBox = m_webPage->m_inputHandler->boundingBoxForInputField();
    if (!inputBoundingBox.isEmpty()) {
        // Adjust the bounding box to the frame offset.
        inputBoundingBox = m_webPage->mainFrame()->view()->windowToContents(frame->view()->contentsToWindow(inputBoundingBox));
        clipRect.intersect(inputBoundingBox);
    }
    return clipRect;
}

void SelectionHandler::regionForTextQuads(Vector<FloatQuad> &quadList, IntRectRegion& region, bool shouldClipToVisibleContent) const
{
    ASSERT(region.isEmpty());

    if (!quadList.isEmpty()) {
        FrameView* frameView = m_webPage->focusedOrMainFrame()->view();

        // frameRect is in frame coordinates.
        WebCore::IntRect frameRect(WebCore::IntPoint(0, 0), frameView->contentsSize());

        // framePosition is in main frame coordinates.
        WebCore::IntPoint framePosition = m_webPage->frameOffset(m_webPage->focusedOrMainFrame());

        // Get the visibile content rect.
        WebCore::IntRect clippingRect = shouldClipToVisibleContent ? clippingRectForVisibleContent() : WebCore::IntRect(-1, -1, 0, 0);

        // Convert the text quads into a more platform friendy
        // IntRectRegion and adjust for subframes.
        Platform::IntRect selectionBoundingBox;
        std::vector<Platform::IntRect> adjustedIntRects;
        for (unsigned i = 0; i < quadList.size(); i++) {
            WebCore::IntRect enclosingRect = quadList[i].enclosingBoundingBox();
            enclosingRect.intersect(frameRect);
            enclosingRect.move(framePosition.x(), framePosition.y());

            // Clip to the visible content.
            if (clippingRect.location() != DOMSupport::InvalidPoint)
                enclosingRect.intersect(clippingRect);

            adjustedIntRects.push_back(enclosingRect);
            selectionBoundingBox = unionOfRects(enclosingRect, selectionBoundingBox);
        }
        region = IntRectRegion(selectionBoundingBox, adjustedIntRects.size(), adjustedIntRects);
    }
}

static VisiblePosition visiblePositionForPointIgnoringClipping(const Frame& frame, const WebCore::IntPoint& framePoint)
{
    // Frame::visiblePositionAtPoint hard-codes ignoreClipping=false in the
    // call to hitTestResultAtPoint. This has a bug where some pages (such as
    // metafilter) will return the wrong VisiblePosition for points that are
    // outside the visible rect. To work around the bug, this is a copy of
    // visiblePositionAtPoint which which passes ignoreClipping=true.
    // See RIM Bug #4315.
    HitTestResult result = frame.eventHandler()->hitTestResultAtPoint(framePoint, true /* allowShadowContent */, true /* ignoreClipping */);

    Node* node = result.innerNode();
    if (!node || node->document() != frame.document())
        return VisiblePosition();

    RenderObject* renderer = node->renderer();
    if (!renderer)
        return VisiblePosition();

    VisiblePosition visiblePos = renderer->positionForPoint(result.localPoint());
    if (visiblePos.isNull())
        visiblePos = VisiblePosition(Position(createLegacyEditingPosition(node, 0)));

    return visiblePos;
}

static unsigned short directionOfPointRelativeToRect(const WebCore::IntPoint& point, const WebCore::IntRect& rect, const bool useTopPadding = true, const bool useBottomPadding = true)
{
    ASSERT(!rect.contains(point));

    // Padding to prevent accidental trigger of up/down when intending to do horizontal movement.
    const int verticalPadding = 5;

    // Do height movement check first but add padding. We may be off on both x & y axis and only
    // want to move in one direction at a time.
    if (point.y() + (useTopPadding ? verticalPadding : 0) < rect.y())
        return KEYCODE_UP;
    if (point.y() > rect.maxY() + (useBottomPadding ? verticalPadding : 0))
        return KEYCODE_DOWN;
    if (point.x() < rect.location().x())
        return KEYCODE_LEFT;
    if (point.x() > rect.maxX())
        return KEYCODE_RIGHT;

    return 0;
}

bool SelectionHandler::shouldUpdateSelectionOrCaretForPoint(const WebCore::IntPoint& point, const WebCore::IntRect& caretRect, bool startCaret) const
{
    ASSERT(m_webPage->m_inputHandler->isInputMode());

    // If the point isn't valid don't block change as it is not actually changing.
    if (point == DOMSupport::InvalidPoint)
        return true;

    VisibleSelection currentSelection = m_webPage->focusedOrMainFrame()->selection()->selection();

    // If the input field is single line or we are on the first or last
    // line of a multiline input field only horizontal movement is supported.
    bool aboveCaret = point.y() < caretRect.y();
    bool belowCaret = point.y() >= caretRect.maxY();

    DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::shouldUpdateSelectionOrCaretForPoint multiline = %s above = %s below = %s first line = %s last line = %s start = %s \n"
            , m_webPage->m_inputHandler->isMultilineInputMode() ? "true" : "false", aboveCaret ? "true" : "false", belowCaret ? "true" : "false"
            , inSameLine(currentSelection.visibleStart(), startOfEditableContent(currentSelection.visibleStart())) ? "true" : "false"
            , inSameLine(currentSelection.visibleEnd(), endOfEditableContent(currentSelection.visibleEnd())) ? "true" : "false"
            , startCaret ? "true" : "false");

    if (!m_webPage->m_inputHandler->isMultilineInputMode() && (aboveCaret || belowCaret))
        return false;
    if (startCaret && inSameLine(currentSelection.visibleStart(), startOfEditableContent(currentSelection.visibleStart())) && aboveCaret)
        return false;
    if (!startCaret && inSameLine(currentSelection.visibleEnd(), endOfEditableContent(currentSelection.visibleEnd())) && belowCaret)
        return false;

    return true;
}

void SelectionHandler::setCaretPosition(const WebCore::IntPoint &position)
{
    if (!m_webPage->m_inputHandler->isInputMode())
        return;

    m_caretActive = true;

    DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::setCaretPosition requested point %d, %d", position.x(), position.y());

    Frame* focusedFrame = m_webPage->focusedOrMainFrame();
    FrameSelection* controller = focusedFrame->selection();
    WebCore::IntPoint relativePoint = DOMSupport::convertPointToFrame(m_webPage->mainFrame(), focusedFrame, position);
    WebCore::IntRect currentCaretRect = controller->selection().visibleStart().absoluteCaretBounds();

    if (relativePoint == DOMSupport::InvalidPoint || !shouldUpdateSelectionOrCaretForPoint(relativePoint, currentCaretRect)) {
        selectionPositionChanged();
        return;
    }

    VisiblePosition visibleCaretPosition(focusedFrame->visiblePositionForPoint(relativePoint));

    if (!DOMSupport::isPositionInNode(m_webPage->focusedOrMainFrame()->document()->focusedNode(), visibleCaretPosition.deepEquivalent())) {
        if (unsigned short character = directionOfPointRelativeToRect(relativePoint, currentCaretRect))
            m_webPage->m_inputHandler->handleKeyboardInput(Platform::KeyboardEvent(character));

        selectionPositionChanged();
        return;
    }

    VisibleSelection newSelection(visibleCaretPosition);
    if (controller->selection() == newSelection) {
        selectionPositionChanged();
        return;
    }

    controller->setSelection(newSelection);

    DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::setCaretPosition point valid, cursor updated");
}

// This function makes sure we are not reducing the selection to a caret selection.
static bool shouldExtendSelectionInDirection(const VisibleSelection& selection, unsigned short character)
{
    FrameSelection tempSelection;
    tempSelection.setSelection(selection);
    switch (character) {
    case KEYCODE_LEFT:
        tempSelection.modify(FrameSelection::AlterationExtend, DirectionLeft, CharacterGranularity);
        break;
    case KEYCODE_RIGHT:
        tempSelection.modify(FrameSelection::AlterationExtend, DirectionRight, CharacterGranularity);
        break;
    case KEYCODE_UP:
        tempSelection.modify(FrameSelection::AlterationExtend, DirectionBackward, LineGranularity);
        break;
    case KEYCODE_DOWN:
        tempSelection.modify(FrameSelection::AlterationExtend, DirectionForward, LineGranularity);
        break;
    default:
        break;
    }

    if ((character == KEYCODE_LEFT || character == KEYCODE_RIGHT)
        && (!inSameLine(selection.visibleStart(), tempSelection.selection().visibleStart())
           || !inSameLine(selection.visibleEnd(), tempSelection.selection().visibleEnd())))
        return false;

    return tempSelection.selection().selectionType() == VisibleSelection::RangeSelection;
}

static int clamp(const int min, const int value, const int max)
{
    return value < min ? min : std::min(value, max);
}

static VisiblePosition directionalVisiblePositionAtExtentOfBox(Frame* frame, const WebCore::IntRect& boundingBox, unsigned short direction, const WebCore::IntPoint& basePoint)
{
    ASSERT(frame);

    if (!frame)
        return VisiblePosition();

    switch (direction) {
    case KEYCODE_LEFT:
        // Extend x to start and clamp y to the edge of bounding box.
        return frame->visiblePositionForPoint(WebCore::IntPoint(boundingBox.x(), clamp(boundingBox.y(), basePoint.y(), boundingBox.maxY())));
    case KEYCODE_RIGHT:
        // Extend x to end and clamp y to the edge of bounding box.
        return frame->visiblePositionForPoint(WebCore::IntPoint(boundingBox.maxX(), clamp(boundingBox.y(), basePoint.y(), boundingBox.maxY())));
    case KEYCODE_UP:
        // Extend y to top and clamp x to the edge of bounding box.
        return frame->visiblePositionForPoint(WebCore::IntPoint(clamp(boundingBox.x(), basePoint.x(), boundingBox.maxX()), boundingBox.y()));
    case KEYCODE_DOWN:
        // Extend y to bottom and clamp x to the edge of bounding box.
        return frame->visiblePositionForPoint(WebCore::IntPoint(clamp(boundingBox.x(), basePoint.x(), boundingBox.maxX()), boundingBox.maxY()));
    default:
        break;
    }

    return frame->visiblePositionForPoint(WebCore::IntPoint(basePoint.x(), basePoint.y()));
}

static bool pointIsOutsideOfBoundingBoxInDirection(unsigned direction, const WebCore::IntPoint& selectionPoint, const WebCore::IntRect& boundingBox)
{
    if ((direction == KEYCODE_LEFT && selectionPoint.x() < boundingBox.x())
        || (direction == KEYCODE_UP && selectionPoint.y() < boundingBox.y())
        || (direction == KEYCODE_RIGHT && selectionPoint.x() > boundingBox.maxX())
        || (direction == KEYCODE_DOWN && selectionPoint.y() > boundingBox.maxY()))
        return true;

    return false;
}

unsigned short SelectionHandler::extendSelectionToFieldBoundary(bool isStartHandle, const WebCore::IntPoint& selectionPoint, VisibleSelection& newSelection)
{
    Frame* focusedFrame = m_webPage->focusedOrMainFrame();
    if (!focusedFrame->document()->focusedNode() || !focusedFrame->document()->focusedNode()->renderer())
        return 0;

    FrameSelection* controller = focusedFrame->selection();

    WebCore::IntRect caretRect = isStartHandle ? controller->selection().visibleStart().absoluteCaretBounds()
                                      : controller->selection().visibleEnd().absoluteCaretBounds();

    WebCore::IntRect nodeBoundingBox = focusedFrame->document()->focusedNode()->renderer()->absoluteBoundingBoxRect();
    nodeBoundingBox.inflate(-1);

    // Start handle is outside of the field. Treat it as the changed handle and move
    // relative to the start caret rect.
    unsigned short character = directionOfPointRelativeToRect(selectionPoint, caretRect, isStartHandle /* useTopPadding */, !isStartHandle /* useBottomPadding */);

    // Prevent incorrect movement, handles can only extend the selection this way
    // to prevent inversion of the handles.
    if (isStartHandle && (character == KEYCODE_RIGHT || character == KEYCODE_DOWN)
        || !isStartHandle && (character == KEYCODE_LEFT || character == KEYCODE_UP))
        character = 0;

    VisiblePosition newVisiblePosition = isStartHandle ? controller->selection().extent() : controller->selection().base();
    // Extend the selection to the bounds of the box before doing incremental scroll if the point is outside the node.
    // Don't extend selection and handle the character at the same time.
    if (pointIsOutsideOfBoundingBoxInDirection(character, selectionPoint, nodeBoundingBox))
        newVisiblePosition = directionalVisiblePositionAtExtentOfBox(focusedFrame, nodeBoundingBox, character, selectionPoint);

    if (isStartHandle)
        newSelection = VisibleSelection(newVisiblePosition, newSelection.extent(), true /* isDirectional */);
    else
        newSelection = VisibleSelection(newSelection.base(), newVisiblePosition, true /* isDirectional */);

    // If no selection will be changed, return the character to extend using navigation.
    if (controller->selection() == newSelection)
        return character;

    // Selection has been updated.
    return 0;
}

// Returns true if handled.
bool SelectionHandler::updateOrHandleInputSelection(VisibleSelection& newSelection, const WebCore::IntPoint& relativeStart
                                                    , const WebCore::IntPoint& relativeEnd)
{
    ASSERT(m_webPage->m_inputHandler->isInputMode());

    Frame* focusedFrame = m_webPage->focusedOrMainFrame();
    Node* focusedNode = focusedFrame->document()->focusedNode();
    if (!focusedNode || !focusedNode->renderer())
        return false;

    FrameSelection* controller = focusedFrame->selection();

    WebCore::IntRect currentStartCaretRect = controller->selection().visibleStart().absoluteCaretBounds();
    WebCore::IntRect currentEndCaretRect = controller->selection().visibleEnd().absoluteCaretBounds();

    // Check if the handle movement is valid.
    if (!shouldUpdateSelectionOrCaretForPoint(relativeStart, currentStartCaretRect, true /* startCaret */)
        || !shouldUpdateSelectionOrCaretForPoint(relativeEnd, currentEndCaretRect, false /* startCaret */)) {
        selectionPositionChanged();
        return true;
    }

    WebCore::IntRect nodeBoundingBox = focusedNode->renderer()->absoluteBoundingBoxRect();

    // Only do special handling if one handle is outside of the node.
    bool startIsOutsideOfField = relativeStart != DOMSupport::InvalidPoint && !nodeBoundingBox.contains(relativeStart);
    bool endIsOutsideOfField = relativeEnd != DOMSupport::InvalidPoint && !nodeBoundingBox.contains(relativeEnd);
    if (startIsOutsideOfField && endIsOutsideOfField)
        return false;

    unsigned short character = 0;
    if (startIsOutsideOfField) {
        character = extendSelectionToFieldBoundary(true /* isStartHandle */, relativeStart, newSelection);
        if (character) {
            // Invert the selection so that the cursor point is at the beginning.
            controller->setSelection(VisibleSelection(controller->selection().end(), controller->selection().start(), true /* isDirectional */));
        }
    } else if (endIsOutsideOfField) {
        character = extendSelectionToFieldBoundary(false /* isStartHandle */, relativeEnd, newSelection);
        if (character) {
            // Reset the selection so that the end is the edit point.
            controller->setSelection(VisibleSelection(controller->selection().start(), controller->selection().end(), true /* isDirectional */));
        }
    }

    if (!character)
        return false;

    DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::setSelection making selection change attempt using key event %d", character);

    if (shouldExtendSelectionInDirection(controller->selection(), character))
        m_webPage->m_inputHandler->handleKeyboardInput(Platform::KeyboardEvent(character, Platform::KeyboardEvent::KeyDown, KEYMOD_SHIFT));

    // Must send the selectionPositionChanged every time, sometimes this will duplicate but an accepted
    // handleNavigationMove may not make an actual selection change.
    selectionPositionChanged();
    return true;
}

void SelectionHandler::setSelection(const WebCore::IntPoint& start, const WebCore::IntPoint& end)
{
    m_selectionActive = true;

    ASSERT(m_webPage);
    ASSERT(m_webPage->focusedOrMainFrame());
    ASSERT(m_webPage->focusedOrMainFrame()->selection());

    Frame* focusedFrame = m_webPage->focusedOrMainFrame();
    FrameSelection* controller = focusedFrame->selection();

    DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::setSelection adjusted points %d, %d, %d, %d", start.x(), start.y(), end.x(), end.y());

    // Note that IntPoint(-1, -1) is being our sentinel so far for
    // clipped out selection starting or ending location.
    bool startIsValid = start != DOMSupport::InvalidPoint;
    m_lastUpdatedEndPointIsValid = end != DOMSupport::InvalidPoint;

    // At least one of the locations must be valid.
    ASSERT(startIsValid || m_lastUpdatedEndPointIsValid);

    WebCore::IntPoint relativeStart = start;
    WebCore::IntPoint relativeEnd = end;

    VisibleSelection newSelection(controller->selection());

    // We need the selection to be ordered base then extent.
    if (!controller->selection().isBaseFirst())
        controller->setSelection(VisibleSelection(controller->selection().start(), controller->selection().end(), true /* isDirectional */));

    // We don't return early in the following, so that we can do input field scrolling if the
    // handle is outside the bounds of the field. This can be extended to handle sub-region
    // scrolling as well
    if (startIsValid) {
        relativeStart = DOMSupport::convertPointToFrame(m_webPage->mainFrame(), focusedFrame, start);

        VisiblePosition base = visiblePositionForPointIgnoringClipping(*focusedFrame, clipPointToVisibleContainer(start));
        if (base.isNotNull()) {
            // The function setBase validates the "base"
            newSelection.setBase(base);
            newSelection.setWithoutValidation(newSelection.base(), controller->selection().end());
            // Don't return early.
        }
    }

    if (m_lastUpdatedEndPointIsValid) {
        relativeEnd = DOMSupport::convertPointToFrame(m_webPage->mainFrame(), focusedFrame, end);

        VisiblePosition extent = visiblePositionForPointIgnoringClipping(*focusedFrame, clipPointToVisibleContainer(end));
        if (extent.isNotNull()) {
            // The function setExtent validates the "extent"
            newSelection.setExtent(extent);
            newSelection.setWithoutValidation(controller->selection().start(), newSelection.extent());
            // Don't return early.
        }
    }

    newSelection.setIsDirectional(true);

    if (m_webPage->m_inputHandler->isInputMode()) {
        if (updateOrHandleInputSelection(newSelection, relativeStart, relativeEnd))
            return;
    }

    if (controller->selection() == newSelection) {
        selectionPositionChanged();
        return;
    }

    // If the selection size is reduce to less than a character, selection type becomes
    // Caret. As long as it is still a range, it's a valid selection. Selection cannot
    // be cancelled through this function.
    Vector<FloatQuad> quads;
    DOMSupport::visibleTextQuads(newSelection, quads);

    IntRectRegion unclippedRegion;
    regionForTextQuads(quads, unclippedRegion, false /* shouldClipToVisibleContent */);
    if (!unclippedRegion.isEmpty()) {
        // Check if the handles reversed position.
        if (m_selectionActive && !newSelection.isBaseFirst())
            m_webPage->m_client->notifySelectionHandlesReversed();

        controller->setSelection(newSelection);

        DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::setSelection selection points valid, selection updated\n");
    } else {
        // Requested selection results in an empty selection, skip this change.
        selectionPositionChanged();

        DEBUG_SELECTION(LogLevelWarn, "SelectionHandler::setSelection selection points invalid, selection not updated\n");
    }
}

// FIXME re-use this in context. Must be updated to include an option to return the href.
// This function should be moved to a new unit file. Names suggetions include DOMQueries
// and NodeTypes. Functions currently in InputHandler.cpp, SelectionHandler.cpp and WebPage.cpp
// can all be moved in.
static Node* enclosingLinkEventParentForNode(Node* node)
{
    if (!node)
        return 0;

    Node* linkNode = node->enclosingLinkEventParentOrSelf();
    return linkNode && linkNode->isLink() ? linkNode : 0;
}

void SelectionHandler::selectAtPoint(const WebCore::IntPoint& location)
{
    // If point is invalid trigger selection based expansion.
    if (location == DOMSupport::InvalidPoint) {
        selectObject(WordGranularity);
        return;
    }

    Node* targetNode;
    WebCore::IntPoint targetPosition;
    // FIXME: Factory this get right fat finger code into a helper.
    const FatFingersResult lastFatFingersResult = m_webPage->m_touchEventHandler->lastFatFingersResult();
    if (lastFatFingersResult.positionWasAdjusted() && lastFatFingersResult.nodeAsElementIfApplicable()) {
        targetNode = lastFatFingersResult.node(FatFingersResult::ShadowContentNotAllowed);
        targetPosition = lastFatFingersResult.adjustedPosition();
    } else {
        FatFingersResult newFatFingersResult = FatFingers(m_webPage, location, FatFingers::Text).findBestPoint();
        if (!newFatFingersResult.positionWasAdjusted())
            return;

        targetPosition = newFatFingersResult.adjustedPosition();
        targetNode = newFatFingersResult.node(FatFingersResult::ShadowContentNotAllowed);
    }

    ASSERT(targetNode);

    // If the node at the point is a link, focus on the entire link, not a word.
    if (Node* link = enclosingLinkEventParentForNode(targetNode)) {
        selectObject(link);
        return;
    }

    // selectAtPoint API currently only supports WordGranularity but may be extended in the future.
    selectObject(targetPosition, WordGranularity);
}

static bool expandSelectionToGranularity(Frame* frame, VisibleSelection selection, TextGranularity granularity, bool isInputMode)
{
    ASSERT(frame);
    ASSERT(frame->selection());

    if (!(selection.start().anchorNode() && selection.start().anchorNode()->isTextNode()))
        return false;

    if (granularity == WordGranularity)
        selection = DOMSupport::visibleSelectionForClosestActualWordStart(selection);

    selection.expandUsingGranularity(granularity);
    RefPtr<Range> newRange = selection.toNormalizedRange();
    RefPtr<Range> oldRange = frame->selection()->selection().toNormalizedRange();
    EAffinity affinity = frame->selection()->affinity();

    if (isInputMode && !frame->editor()->client()->shouldChangeSelectedRange(oldRange.get(), newRange.get(), affinity, false))
        return false;

    return frame->selection()->setSelectedRange(newRange.get(), affinity, true);
}

void SelectionHandler::selectObject(const WebCore::IntPoint& location, TextGranularity granularity)
{
    ASSERT(location.x() >= 0 && location.y() >= 0);
    ASSERT(m_webPage && m_webPage->focusedOrMainFrame() && m_webPage->focusedOrMainFrame()->selection());
    Frame* focusedFrame = m_webPage->focusedOrMainFrame();

    DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::selectObject adjusted points %d, %d", location.x(), location.y());

    WebCore::IntPoint relativePoint = DOMSupport::convertPointToFrame(m_webPage->mainFrame(), focusedFrame, location);
    VisiblePosition pointLocation(focusedFrame->visiblePositionForPoint(relativePoint));
    VisibleSelection selection = VisibleSelection(pointLocation, pointLocation);

    m_selectionActive = expandSelectionToGranularity(focusedFrame, selection, granularity, m_webPage->m_inputHandler->isInputMode());
}

void SelectionHandler::selectObject(TextGranularity granularity)
{
    ASSERT(m_webPage && m_webPage->m_inputHandler);
    // Using caret location, must be inside an input field.
    if (!m_webPage->m_inputHandler->isInputMode())
        return;

    ASSERT(m_webPage->focusedOrMainFrame() && m_webPage->focusedOrMainFrame()->selection());
    Frame* focusedFrame = m_webPage->focusedOrMainFrame();

    DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::selectObject using current selection");

    // Use the current selection as the selection point.
    ASSERT(focusedFrame->selection()->selectionType() != VisibleSelection::NoSelection);
    m_selectionActive = expandSelectionToGranularity(focusedFrame, focusedFrame->selection()->selection(), granularity, true /* isInputMode */);
}

void SelectionHandler::selectObject(Node* node)
{
    if (!node)
        return;

    m_selectionActive = true;

    ASSERT(m_webPage && m_webPage->focusedOrMainFrame() && m_webPage->focusedOrMainFrame()->selection());
    Frame* focusedFrame = m_webPage->focusedOrMainFrame();

    DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::selectNode");

    VisibleSelection selection = VisibleSelection::selectionFromContentsOfNode(node);
    focusedFrame->selection()->setSelection(selection);
}

static TextDirection directionOfEnclosingBlock(FrameSelection* selection)
{
    Node* enclosingBlockNode = enclosingBlock(selection->selection().extent().deprecatedNode());
    if (!enclosingBlockNode)
        return LTR;

    if (RenderObject* renderer = enclosingBlockNode->renderer())
        return renderer->style()->direction();

    return LTR;
}

// Returns > 0 if p1 is "closer" to referencePoint, < 0 if p2 is "closer", 0 if they are equidistant.
// Because text is usually arranged in horizontal rows, distance is measured along the y-axis, with x-axis used only to break ties.
// If rightGravity is true, the right-most x-coordinate is chosen, otherwise teh left-most coordinate is chosen.
static inline int comparePointsToReferencePoint(const WebCore::IntPoint& p1, const WebCore::IntPoint& p2, const WebCore::IntPoint& referencePoint, bool rightGravity)
{
    int dy1 = abs(referencePoint.y() - p1.y());
    int dy2 = abs(referencePoint.y() - p2.y());
    if (dy1 != dy2)
        return dy2 - dy1;

    // Same y-coordinate, choose the farthest right (or left) point.
    if (p1.x() == p2.x())
        return 0;

    if (p1.x() > p2.x())
        return rightGravity ? 1 : -1;

    return rightGravity ? -1 : 1;
}

// NOTE/FIXME: Due to r77286, we are getting off-by-one results in the IntRect class counterpart implementation of the
//             methods below. As done in r89803, r77928 and a few others, lets use local method to fix it.
//             We should keep our eyes very open on it, since it can affect BackingStore very badly.
static WebCore::IntPoint minXMinYCorner(const WebCore::IntRect& rect) { return rect.location(); } // typically topLeft
static WebCore::IntPoint maxXMinYCorner(const WebCore::IntRect& rect) { return WebCore::IntPoint(rect.x() + rect.width() - 1, rect.y()); } // typically topRight
static WebCore::IntPoint minXMaxYCorner(const WebCore::IntRect& rect) { return WebCore::IntPoint(rect.x(), rect.y() + rect.height() - 1); } // typically bottomLeft
static WebCore::IntPoint maxXMaxYCorner(const WebCore::IntRect& rect) { return WebCore::IntPoint(rect.x() + rect.width() - 1, rect.y() + rect.height() - 1); } // typically bottomRight

// The caret is a one-pixel wide line down either the right or left edge of a
// rect, depending on the text direction.
static inline bool caretIsOnLeft(bool isStartCaret, bool isRTL)
{
    if (isStartCaret)
        return !isRTL;

    return isRTL;
}

static inline WebCore::IntPoint caretLocationForRect(const WebCore::IntRect& rect, bool isStartCaret, bool isRTL)
{
    return caretIsOnLeft(isStartCaret, isRTL) ? minXMinYCorner(rect) : maxXMinYCorner(rect);
}

static inline WebCore::IntPoint caretComparisonPointForRect(const WebCore::IntRect& rect, bool isStartCaret, bool isRTL)
{
    if (isStartCaret)
        return caretIsOnLeft(isStartCaret, isRTL) ? minXMinYCorner(rect) : maxXMinYCorner(rect);

    return caretIsOnLeft(isStartCaret, isRTL) ? minXMaxYCorner(rect) : maxXMaxYCorner(rect);
}

static void adjustCaretRects(WebCore::IntRect& startCaret, bool isStartCaretClippedOut,
                             WebCore::IntRect& endCaret, bool isEndCaretClippedOut,
                             const std::vector<Platform::IntRect> rectList,
                             const WebCore::IntPoint& startReferencePoint,
                             const WebCore::IntPoint& endReferencePoint,
                             bool isRTL)
{
    // startReferencePoint is the best guess at the top left of the selection; endReferencePoint is the best guess at the bottom right.
    if (isStartCaretClippedOut)
        startCaret.setLocation(DOMSupport::InvalidPoint);
    else {
        startCaret = rectList[0];
        startCaret.setLocation(caretLocationForRect(startCaret, true, isRTL));
    }

    if (isEndCaretClippedOut)
        endCaret.setLocation(DOMSupport::InvalidPoint);
    else {
        endCaret = rectList[0];
        endCaret.setLocation(caretLocationForRect(endCaret, false, isRTL));
    }

    if (isStartCaretClippedOut && isEndCaretClippedOut)
        return;

    // Reset width to 1 as we are strictly interested in caret location.
    startCaret.setWidth(1);
    endCaret.setWidth(1);

    for (unsigned i = 1; i < rectList.size(); i++) {
        WebCore::IntRect currentRect(rectList[i]);

        // Compare and update the start and end carets with their respective reference points.
        if (!isStartCaretClippedOut && comparePointsToReferencePoint(
                    caretComparisonPointForRect(currentRect, true, isRTL),
                    caretComparisonPointForRect(startCaret, true, isRTL),
                    startReferencePoint, isRTL) > 0) {
            startCaret.setLocation(caretLocationForRect(currentRect, true, isRTL));
            startCaret.setHeight(currentRect.height());
        }

        if (!isEndCaretClippedOut && comparePointsToReferencePoint(
                    caretComparisonPointForRect(currentRect, false, isRTL),
                    caretComparisonPointForRect(endCaret, false, isRTL),
                    endReferencePoint, !isRTL) > 0) {
            endCaret.setLocation(caretLocationForRect(currentRect, false, isRTL));
            endCaret.setHeight(currentRect.height());
        }
    }
}

WebCore::IntPoint SelectionHandler::clipPointToVisibleContainer(const WebCore::IntPoint& point) const
{
    ASSERT(m_webPage->m_mainFrame && m_webPage->m_mainFrame->view());

    Frame* frame = m_webPage->focusedOrMainFrame();
    WebCore::IntPoint clippedPoint = DOMSupport::convertPointToFrame(m_webPage->mainFrame(), frame, point, true /* clampToTargetFrame */);

    if (m_webPage->m_inputHandler->isInputMode()
            && frame->document()->focusedNode()
            && frame->document()->focusedNode()->renderer()) {
        WebCore::IntRect boundingBox(frame->document()->focusedNode()->renderer()->absoluteBoundingBoxRect());
        boundingBox.inflate(-1);
        clippedPoint = WebCore::IntPoint(clamp(boundingBox.x(), clippedPoint.x(), boundingBox.maxX()), clamp(boundingBox.y(), clippedPoint.y(), boundingBox.maxY()));
    }

    return clippedPoint;
}

static WebCore::IntPoint referencePoint(const VisiblePosition& position, const WebCore::IntRect& boundingRect, const WebCore::IntPoint& framePosition, bool isStartCaret, bool isRTL)
{
    // If one of the carets is invalid (this happens, for instance, if the
    // selection ends in an empty div) fall back to using the corner of the
    // entire region (which is already in frame coordinates so doesn't need
    // adjusting).
    WebCore::IntRect startCaretBounds(position.absoluteCaretBounds());
    if (startCaretBounds.isEmpty())
        startCaretBounds = boundingRect;
    else
        startCaretBounds.move(framePosition.x(), framePosition.y());

    return caretComparisonPointForRect(startCaretBounds, isStartCaret, isRTL);
}

// Check all rects in the region for a point match. The region is non-banded
// and non-sorted so all must be checked.
static bool regionRectListContainsPoint(const IntRectRegion& region, const WebCore::IntPoint& point)
{
    if (!region.extents().contains(point))
        return false;

    std::vector<Platform::IntRect> rectList = region.rects();
    for (unsigned int i = 0; i < rectList.size(); i++) {
        if (rectList[i].contains(point))
            return true;
    }
    return false;
}

bool SelectionHandler::inputNodeOverridesTouch() const
{
    if (!m_webPage->m_inputHandler->isInputMode())
        return false;

    Node* focusedNode = m_webPage->focusedOrMainFrame()->document()->focusedNode();
    if (!focusedNode || !focusedNode->isElementNode())
        return false;

    // TODO consider caching this in InputHandler so it is only calculated once per focus.
    DEFINE_STATIC_LOCAL(QualifiedName, selectionTouchOverrideAttr, (nullAtom, "data-blackberry-end-selection-on-touch", nullAtom));
    Element* element = static_cast<Element*>(focusedNode);
    return DOMSupport::elementAttributeState(element, selectionTouchOverrideAttr) == DOMSupport::On;
}

// Note: This is the only function in SelectionHandler in which the coordinate
// system is not entirely WebKit.
void SelectionHandler::selectionPositionChanged(bool visualChangeOnly)
{
    DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::selectionPositionChanged visibleChangeOnly = %s", visualChangeOnly ? "true" : "false");

    // This method can get called during WebPage shutdown process.
    // If that is the case, just bail out since the client is not
    // in a safe state of trust to request anything else from it.
    if (!m_webPage->m_mainFrame)
        return;

    if (m_webPage->m_inputHandler->isInputMode() && m_webPage->m_inputHandler->processingChange()) {
        m_webPage->m_client->cancelSelectionVisuals();
        return;
    }

    if (m_caretActive || (m_webPage->m_inputHandler->isInputMode() && m_webPage->focusedOrMainFrame()->selection()->isCaret())) {
        // This may update the caret to no longer be active.
        caretPositionChanged();
    }

    // Enter selection mode if selection type is RangeSelection, and disable selection if
    // selection is active and becomes caret selection.
    Frame* frame = m_webPage->focusedOrMainFrame();
    WebCore::IntPoint framePos = m_webPage->frameOffset(frame);
    if (m_selectionActive && (m_caretActive || frame->selection()->isNone()))
        m_selectionActive = false;
    else if (frame->selection()->isRange())
        m_selectionActive = true;
    else if (!m_selectionActive)
        return;

    WebCore::IntRect startCaret;
    WebCore::IntRect endCaret;

    // Get the text rects from the selections range.
    Vector<FloatQuad> quads;
    DOMSupport::visibleTextQuads(frame->selection()->selection(), quads);

    IntRectRegion unclippedRegion;
    regionForTextQuads(quads, unclippedRegion, false /* shouldClipToVisibleContent */);

    // If there is no change in selected text and the visual rects
    // have not changed then don't bother notifying anything.
    if (visualChangeOnly && m_lastSelectionRegion.isEqual(unclippedRegion))
        return;

    m_lastSelectionRegion = unclippedRegion;

    IntRectRegion visibleSelectionRegion;
    if (!unclippedRegion.isEmpty()) {
        WebCore::IntRect unclippedStartCaret;
        WebCore::IntRect unclippedEndCaret;

        bool isRTL = directionOfEnclosingBlock(frame->selection()) == RTL;

        WebCore::IntPoint startCaretReferencePoint = referencePoint(frame->selection()->selection().visibleStart(), unclippedRegion.extents(), framePos, true /* isStartCaret */, isRTL);
        WebCore::IntPoint endCaretReferencePoint = referencePoint(frame->selection()->selection().visibleEnd(), unclippedRegion.extents(), framePos, false /* isStartCaret */, isRTL);

        adjustCaretRects(unclippedStartCaret, false /* unclipped */, unclippedEndCaret, false /* unclipped */, unclippedRegion.rects(), startCaretReferencePoint, endCaretReferencePoint, isRTL);

        regionForTextQuads(quads, visibleSelectionRegion);

#if SHOWDEBUG_SELECTIONHANDLER // Don't rely just on DEBUG_SELECTION to avoid loop.
        for (unsigned int i = 0; i < unclippedRegion.numRects(); i++)
            DEBUG_SELECTION(LogLevelCritical, "Rect list - Unmodified #%d, (%d, %d) (%d x %d)", i, unclippedRegion.rects()[i].x(), unclippedRegion.rects()[i].y(), unclippedRegion.rects()[i].width(), unclippedRegion.rects()[i].height());
        for (unsigned int i = 0; i < visibleSelectionRegion.numRects(); i++)
            DEBUG_SELECTION(LogLevelCritical, "Rect list  - Clipped to Visible #%d, (%d, %d) (%d x %d)", i, visibleSelectionRegion.rects()[i].x(), visibleSelectionRegion.rects()[i].y(), visibleSelectionRegion.rects()[i].width(), visibleSelectionRegion.rects()[i].height());
#endif

        bool shouldCareAboutPossibleClippedOutSelection = frame != m_webPage->mainFrame() || m_webPage->m_inputHandler->isInputMode();

        if (!visibleSelectionRegion.isEmpty() || shouldCareAboutPossibleClippedOutSelection) {
            // Adjust the handle markers to be at the end of the painted rect. When selecting links
            // and other elements that may have a larger visible area than needs to be rendered a gap
            // can exist between the handle and overlay region.

            bool shouldClipStartCaret = !regionRectListContainsPoint(visibleSelectionRegion, unclippedStartCaret.location());
            bool shouldClipEndCaret = !regionRectListContainsPoint(visibleSelectionRegion, unclippedEndCaret.location());

            // Find the top corner and bottom corner.
            adjustCaretRects(startCaret, shouldClipStartCaret, endCaret, shouldClipEndCaret, visibleSelectionRegion.rects(), startCaretReferencePoint, endCaretReferencePoint, isRTL);

            // Translate the caret values as they must be in transformed coordinates.
            if (!shouldClipStartCaret) {
                startCaret = m_webPage->mapToTransformed(startCaret);
                m_webPage->clipToTransformedContentsRect(startCaret);
            }

            if (!shouldClipEndCaret) {
                endCaret = m_webPage->mapToTransformed(endCaret);
                m_webPage->clipToTransformedContentsRect(endCaret);
            }
        }
    }

    DEBUG_SELECTION(BlackBerry::Platform::LogLevelInfo, "SelectionHandler::selectionPositionChanged Start Rect=(%d, %d) (%d x %d) End Rect=(%d, %d) (%d x %d)",
                    startCaret.x(), startCaret.y(), startCaret.width(), startCaret.height(), endCaret.x(), endCaret.y(), endCaret.width(), endCaret.height());


    m_webPage->m_client->notifySelectionDetailsChanged(startCaret, endCaret, visibleSelectionRegion, inputNodeOverridesTouch());
}

// NOTE: This function is not in WebKit coordinates.
void SelectionHandler::caretPositionChanged()
{
    DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::caretPositionChanged");

    WebCore::IntRect caretLocation;
    // If the input field is not active, we must be turning off the caret.
    if (!m_webPage->m_inputHandler->isInputMode() && m_caretActive) {
        m_caretActive = false;
        // Send an empty caret change to turn off the caret.
        m_webPage->m_client->notifyCaretChanged(caretLocation, m_webPage->m_touchEventHandler->lastFatFingersResult().isTextInput() /* userTouchTriggered */);
        return;
    }

    ASSERT(m_webPage && m_webPage->focusedOrMainFrame() && m_webPage->focusedOrMainFrame()->selection());

    // This function should only reach this point if input mode is active.
    ASSERT(m_webPage->m_inputHandler->isInputMode());

    if (m_webPage->focusedOrMainFrame()->selection()->selectionType() == VisibleSelection::CaretSelection) {
        WebCore::IntPoint frameOffset = m_webPage->frameOffset(m_webPage->focusedOrMainFrame());

        caretLocation = m_webPage->focusedOrMainFrame()->selection()->selection().visibleStart().absoluteCaretBounds();
        caretLocation.move(frameOffset.x(), frameOffset.y());

        // Clip against the containing frame and node boundaries.
        caretLocation.intersect(clippingRectForVisibleContent());
    }

    m_caretActive = !caretLocation.isEmpty();

    DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::caretPositionChanged caret Rect %d, %d, %dx%d",
                        caretLocation.x(), caretLocation.y(), caretLocation.width(), caretLocation.height());

    caretLocation = m_webPage->mapToTransformed(caretLocation);
    m_webPage->clipToTransformedContentsRect(caretLocation);

    bool singleLineInput = !m_webPage->m_inputHandler->isMultilineInputMode();
    WebCore::IntRect nodeBoundingBox = singleLineInput ? m_webPage->m_inputHandler->boundingBoxForInputField() : WebCore::IntRect();

    m_webPage->m_client->notifyCaretChanged(caretLocation, m_webPage->m_touchEventHandler->lastFatFingersResult().isTextInput() /* userTouchTriggered */, singleLineInput, nodeBoundingBox);
}

bool SelectionHandler::selectionContains(const WebCore::IntPoint& point)
{
    ASSERT(m_webPage && m_webPage->focusedOrMainFrame() && m_webPage->focusedOrMainFrame()->selection());
    return m_webPage->focusedOrMainFrame()->selection()->contains(point);
}

}
}