FocusController::setFocusedNode() should be setFocusedElement().
[WebKit-https.git] / Source / WebKit / blackberry / WebKitSupport / SelectionHandler.cpp
index a2e7bfe..f5736f1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010, 2011, 2012 Research In Motion Limited. All rights reserved.
+ * 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
 
 #include "DOMSupport.h"
 #include "Document.h"
-#include "Editor.h"
-#include "EditorClient.h"
 #include "FatFingers.h"
 #include "FloatQuad.h"
+#include "FocusController.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 "RenderLayer.h"
+#include "SelectionOverlay.h"
 #include "TouchEventHandler.h"
-#include "WebPage.h"
+#include "VisibleUnits.h"
 #include "WebPageClient.h"
 #include "WebPage_p.h"
 
 #include "htmlediting.h"
-#include "visible_units.h"
 
 #include <BlackBerryPlatformKeyboardEvent.h>
+#include <BlackBerryPlatformLog.h>
+#include <BlackBerryPlatformViewportAccessor.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
+#define SHOWDEBUG_SELECTIONHANDLER_TIMING 0
 
 using namespace BlackBerry::Platform;
 using namespace WebCore;
 
 #if SHOWDEBUG_SELECTIONHANDLER
-#define DEBUG_SELECTION(severity, format, ...) logAlways(severity, format, ## __VA_ARGS__)
+#define SelectionLog(severity, format, ...) Platform::logAlways(severity, format, ## __VA_ARGS__)
 #else
-#define DEBUG_SELECTION(severity, format, ...)
+#define SelectionLog(severity, format, ...)
 #endif // SHOWDEBUG_SELECTIONHANDLER
 
+#if SHOWDEBUG_SELECTIONHANDLER_TIMING
+#define SelectionTimingLog(severity, format, ...) Platform::logAlways(severity, format, ## __VA_ARGS__)
+#else
+#define SelectionTimingLog(severity, format, ...)
+#endif // SHOWDEBUG_SELECTIONHANDLER_TIMING
+
 namespace BlackBerry {
 namespace WebKit {
 
@@ -69,6 +74,7 @@ SelectionHandler::SelectionHandler(WebPagePrivate* page)
     , m_selectionActive(false)
     , m_caretActive(false)
     , m_lastUpdatedEndPointIsValid(false)
+    , m_didSuppressCaretPositionChangedNotification(false)
 {
 }
 
@@ -81,7 +87,15 @@ void SelectionHandler::cancelSelection()
     m_selectionActive = false;
     m_lastSelectionRegion = IntRectRegion();
 
-    DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::cancelSelection");
+    if (m_webPage->m_selectionOverlay)
+        m_webPage->m_selectionOverlay->hide();
+    // Notify client with empty selection to ensure the handles are removed if
+    // rendering happened prior to processing on webkit thread
+    m_webPage->m_client->notifySelectionDetailsChanged(SelectionDetails());
+
+    m_webPage->updateSelectionScrollView(0);
+
+    SelectionLog(Platform::LogLevelInfo, "SelectionHandler::cancelSelection");
 
     if (m_webPage->m_inputHandler->isInputMode())
         m_webPage->m_inputHandler->cancelSelection();
@@ -89,29 +103,27 @@ void SelectionHandler::cancelSelection()
         m_webPage->focusedOrMainFrame()->selection()->clear();
 }
 
-WebString SelectionHandler::selectedText() const
+BlackBerry::Platform::String SelectionHandler::selectedText() const
 {
-    return m_webPage->focusedOrMainFrame()->editor()->selectedText();
+    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(-1, -1, 0, 0);
+    WebCore::IntRect clipRect = WebCore::IntRect(WebCore::IntPoint(0, 0), frame->view()->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.
-    if (m_webPage->m_inputHandler->isInputMode()
-        && frame->document()->focusedNode()
-        && frame->document()->focusedNode()->renderer()) {
-
+    WebCore::IntRect inputBoundingBox = m_webPage->m_inputHandler->boundingBoxForInputField();
+    if (!inputBoundingBox.isEmpty()) {
         // Adjust the bounding box to the frame offset.
-        clipRect = frame->document()->focusedNode()->renderer()->absoluteBoundingBoxRect();
-        clipRect = m_webPage->mainFrame()->view()->windowToContents(frame->view()->contentsToWindow(clipRect));
+        inputBoundingBox = m_webPage->mainFrame()->view()->windowToContents(frame->view()->contentsToWindow(inputBoundingBox));
+        clipRect.intersect(inputBoundingBox);
     }
     return clipRect;
 }
@@ -134,20 +146,21 @@ void SelectionHandler::regionForTextQuads(Vector<FloatQuad> &quadList, IntRectRe
 
         // Convert the text quads into a more platform friendy
         // IntRectRegion and adjust for subframes.
-        std::vector<Platform::IntRect> adjustedIntRects;
         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());
-            adjustedIntRects.push_back(enclosingRect);
 
             // Clip to the visible content.
             if (clippingRect.location() != DOMSupport::InvalidPoint)
                 enclosingRect.intersect(clippingRect);
 
-            selectionBoundingBox = unionOfRects(enclosingRect, selectionBoundingBox);
+            adjustedIntRects.push_back(enclosingRect);
+            selectionBoundingBox.unite(enclosingRect);
         }
+        region = IntRectRegion(selectionBoundingBox, adjustedIntRects.size(), adjustedIntRects);
     }
 }
 
@@ -159,7 +172,7 @@ static VisiblePosition visiblePositionForPointIgnoringClipping(const Frame& fram
     // 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 */);
+    HitTestResult result = frame.eventHandler()->hitTestResultAtPoint(framePoint, HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::IgnoreClipping);
 
     Node* node = result.innerNode();
     if (!node || node->document() != frame.document())
@@ -176,7 +189,7 @@ static VisiblePosition visiblePositionForPointIgnoringClipping(const Frame& fram
     return visiblePos;
 }
 
-static unsigned short directionOfPointRelativeToRect(const WebCore::IntPoint& point, const WebCore::IntRect& rect, const bool useTopPadding = true, const bool useBottomPadding = true)
+static unsigned directionOfPointRelativeToRect(const WebCore::IntPoint& point, const WebCore::IntRect& rect, const bool useTopPadding = true, const bool useBottomPadding = true)
 {
     ASSERT(!rect.contains(point));
 
@@ -185,7 +198,7 @@ static unsigned short directionOfPointRelativeToRect(const WebCore::IntPoint& po
 
     // 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())
+    if (point.y() - (useTopPadding ? verticalPadding : 0) < rect.y())
         return KEYCODE_UP;
     if (point.y() > rect.maxY() + (useBottomPadding ? verticalPadding : 0))
         return KEYCODE_DOWN;
@@ -212,11 +225,14 @@ bool SelectionHandler::shouldUpdateSelectionOrCaretForPoint(const WebCore::IntPo
     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");
+    SelectionLog(Platform::LogLevelInfo,
+        "SelectionHandler::shouldUpdateSelectionOrCaretForPoint multiline = %s above = %s below = %s first line = %s last line = %s start = %s",
+        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;
@@ -228,14 +244,16 @@ bool SelectionHandler::shouldUpdateSelectionOrCaretForPoint(const WebCore::IntPo
     return true;
 }
 
-void SelectionHandler::setCaretPosition(const WebCore::IntPoint &position)
+void SelectionHandler::setCaretPosition(const WebCore::IntPointposition)
 {
-    if (!m_webPage->m_inputHandler->isInputMode())
+    if (!m_webPage->m_inputHandler->isInputMode() || !m_webPage->focusedOrMainFrame()->document()->focusedNode())
         return;
 
     m_caretActive = true;
 
-    DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::setCaretPosition requested point %d, %d", position.x(), position.y());
+    SelectionLog(Platform::LogLevelInfo,
+        "SelectionHandler::setCaretPosition requested point %s",
+        Platform::IntPoint(position).toString().c_str());
 
     Frame* focusedFrame = m_webPage->focusedOrMainFrame();
     FrameSelection* controller = focusedFrame->selection();
@@ -243,33 +261,40 @@ void SelectionHandler::setCaretPosition(const WebCore::IntPoint &position)
     WebCore::IntRect currentCaretRect = controller->selection().visibleStart().absoluteCaretBounds();
 
     if (relativePoint == DOMSupport::InvalidPoint || !shouldUpdateSelectionOrCaretForPoint(relativePoint, currentCaretRect)) {
-        selectionPositionChanged();
+        selectionPositionChanged(true /* forceUpdateWithoutChange */);
         return;
     }
 
-    VisiblePosition visibleCaretPosition(focusedFrame->visiblePositionForPoint(relativePoint));
-
-    if (!DOMSupport::isPositionInNode(m_webPage->focusedOrMainFrame()->document()->focusedNode(), visibleCaretPosition.deepEquivalent())) {
-        if (unsigned short character = directionOfPointRelativeToRect(relativePoint, currentCaretRect))
+    WebCore::IntRect nodeOutlineBounds(m_webPage->m_inputHandler->boundingBoxForInputField());
+    if (!nodeOutlineBounds.isEmpty() && !nodeOutlineBounds.contains(relativePoint)) {
+        if (unsigned character = directionOfPointRelativeToRect(relativePoint, currentCaretRect))
             m_webPage->m_inputHandler->handleKeyboardInput(Platform::KeyboardEvent(character));
 
-        selectionPositionChanged();
+        // Send the selection changed in case this does not trigger a selection change to
+        // ensure the caret position is accurate. This may be a duplicate event.
+        selectionPositionChanged(true /* forceUpdateWithoutChange */);
         return;
     }
 
-    VisibleSelection newSelection(visibleCaretPosition);
+    VisibleSelection newSelection(focusedFrame->visiblePositionForPoint(relativePoint));
     if (controller->selection() == newSelection) {
-        selectionPositionChanged();
+        selectionPositionChanged(true /* forceUpdateWithoutChange */);
         return;
     }
 
     controller->setSelection(newSelection);
 
-    DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::setCaretPosition point valid, cursor updated");
+    SelectionLog(Platform::LogLevelInfo, "SelectionHandler::setCaretPosition point valid, cursor updated");
+}
+
+void SelectionHandler::inputHandlerDidFinishProcessingChange()
+{
+    if (m_didSuppressCaretPositionChangedNotification)
+        notifyCaretPositionChangedIfNeeded(false);
 }
 
 // This function makes sure we are not reducing the selection to a caret selection.
-static bool shouldExtendSelectionInDirection(const VisibleSelection& selection, unsigned short character)
+static bool shouldExtendSelectionInDirection(const VisibleSelection& selection, unsigned character)
 {
     FrameSelection tempSelection;
     tempSelection.setSelection(selection);
@@ -292,7 +317,7 @@ static bool shouldExtendSelectionInDirection(const VisibleSelection& selection,
 
     if ((character == KEYCODE_LEFT || character == KEYCODE_RIGHT)
         && (!inSameLine(selection.visibleStart(), tempSelection.selection().visibleStart())
-           || !inSameLine(selection.visibleEnd(), tempSelection.selection().visibleEnd())))
+            || !inSameLine(selection.visibleEnd(), tempSelection.selection().visibleEnd())))
         return false;
 
     return tempSelection.selection().selectionType() == VisibleSelection::RangeSelection;
@@ -303,7 +328,7 @@ 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)
+static VisiblePosition directionalVisiblePositionAtExtentOfBox(Frame* frame, const WebCore::IntRect& boundingBox, unsigned direction, const WebCore::IntPoint& basePoint)
 {
     ASSERT(frame);
 
@@ -341,31 +366,30 @@ static bool pointIsOutsideOfBoundingBoxInDirection(unsigned direction, const Web
     return false;
 }
 
-unsigned short SelectionHandler::extendSelectionToFieldBoundary(bool isStartHandle, const WebCore::IntPoint& selectionPoint, VisibleSelection& newSelection)
+unsigned 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();
+    VisibleSelection activeSelection = focusedFrame->selection()->selection();
 
-    WebCore::IntRect caretRect = isStartHandle ? controller->selection().visibleStart().absoluteCaretBounds()
-                                      : controller->selection().visibleEnd().absoluteCaretBounds();
+    WebCore::IntRect caretRect = isStartHandle ? activeSelection.visibleStart().absoluteCaretBounds() : activeSelection.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 */);
+    unsigned 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))
+    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();
+    VisiblePosition newVisiblePosition = isStartHandle ? activeSelection.extent() : activeSelection.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))
@@ -377,7 +401,7 @@ unsigned short SelectionHandler::extendSelectionToFieldBoundary(bool isStartHand
         newSelection = VisibleSelection(newSelection.base(), newVisiblePosition, true /* isDirectional */);
 
     // If no selection will be changed, return the character to extend using navigation.
-    if (controller->selection() == newSelection)
+    if (activeSelection == newSelection)
         return character;
 
     // Selection has been updated.
@@ -385,8 +409,7 @@ unsigned short SelectionHandler::extendSelectionToFieldBoundary(bool isStartHand
 }
 
 // Returns true if handled.
-bool SelectionHandler::updateOrHandleInputSelection(VisibleSelection& newSelection, const WebCore::IntPoint& relativeStart
-                                                    , const WebCore::IntPoint& relativeEnd)
+bool SelectionHandler::updateOrHandleInputSelection(VisibleSelection& newSelection, const WebCore::IntPoint& relativeStart, const WebCore::IntPoint& relativeEnd)
 {
     ASSERT(m_webPage->m_inputHandler->isInputMode());
 
@@ -403,7 +426,7 @@ bool SelectionHandler::updateOrHandleInputSelection(VisibleSelection& newSelecti
     // Check if the handle movement is valid.
     if (!shouldUpdateSelectionOrCaretForPoint(relativeStart, currentStartCaretRect, true /* startCaret */)
         || !shouldUpdateSelectionOrCaretForPoint(relativeEnd, currentEndCaretRect, false /* startCaret */)) {
-        selectionPositionChanged();
+        selectionPositionChanged(true /* forceUpdateWithoutChange */);
         return true;
     }
 
@@ -415,16 +438,18 @@ bool SelectionHandler::updateOrHandleInputSelection(VisibleSelection& newSelecti
     if (startIsOutsideOfField && endIsOutsideOfField)
         return false;
 
-    unsigned short character = 0;
+    unsigned character = 0;
+    bool needToInvertDirection = false;
     if (startIsOutsideOfField) {
         character = extendSelectionToFieldBoundary(true /* isStartHandle */, relativeStart, newSelection);
-        if (character) {
+        if (character && controller->selection().isBaseFirst()) {
             // Invert the selection so that the cursor point is at the beginning.
             controller->setSelection(VisibleSelection(controller->selection().end(), controller->selection().start(), true /* isDirectional */));
+            needToInvertDirection = true;
         }
     } else if (endIsOutsideOfField) {
         character = extendSelectionToFieldBoundary(false /* isStartHandle */, relativeEnd, newSelection);
-        if (character) {
+        if (character && !controller->selection().isBaseFirst()) {
             // Reset the selection so that the end is the edit point.
             controller->setSelection(VisibleSelection(controller->selection().start(), controller->selection().end(), true /* isDirectional */));
         }
@@ -433,18 +458,23 @@ bool SelectionHandler::updateOrHandleInputSelection(VisibleSelection& newSelecti
     if (!character)
         return false;
 
-    DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::setSelection making selection change attempt using key event %d", character);
+    SelectionLog(Platform::LogLevelInfo,
+        "SelectionHandler::updateOrHandleInputSelection 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();
+    if (needToInvertDirection)
+        controller->setSelection(VisibleSelection(controller->selection().extent(), controller->selection().base(), true /* isDirectional */));
+
+    // Send the selection changed in case this does not trigger a selection change to
+    // ensure the caret position is accurate. This may be a duplicate event.
+    selectionPositionChanged(true /* forceUpdateWithoutChange */);
     return true;
 }
 
-void SelectionHandler::setSelection(const WebCore::IntPoint& start, const WebCore::IntPoint& end)
+void SelectionHandler::setSelection(WebCore::IntPoint start, WebCore::IntPoint end)
 {
     m_selectionActive = true;
 
@@ -455,7 +485,14 @@ void SelectionHandler::setSelection(const WebCore::IntPoint& start, const WebCor
     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());
+#if SHOWDEBUG_SELECTIONHANDLER_TIMING
+    m_timer.start();
+#endif
+
+    SelectionLog(Platform::LogLevelInfo,
+        "SelectionHandler::setSelection adjusted points %s, %s",
+        Platform::IntPoint(start).toString().c_str(),
+        Platform::IntPoint(end).toString().c_str());
 
     // Note that IntPoint(-1, -1) is being our sentinel so far for
     // clipped out selection starting or ending location.
@@ -465,43 +502,45 @@ void SelectionHandler::setSelection(const WebCore::IntPoint& start, const WebCor
     // At least one of the locations must be valid.
     ASSERT(startIsValid || m_lastUpdatedEndPointIsValid);
 
+    if (m_webPage->m_inputHandler->isInputMode() && !m_webPage->m_inputHandler->isMultilineInputMode()) {
+        WebCore::IntRect caret(startCaretViewportRect(m_webPage->frameOffset(focusedFrame)));
+        if (!caret.isEmpty()) {
+            int centerOfCaretY = caret.center().y();
+            if (startIsValid)
+                start.setY(centerOfCaretY);
+            if (m_lastUpdatedEndPointIsValid)
+                end.setY(centerOfCaretY);
+        }
+    }
+
     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 */));
+    // Initialize the new start and end of our selection at the current positions.
+    VisiblePosition newStart = controller->selection().visibleStart();
+    VisiblePosition newEnd = controller->selection().visibleEnd();
 
     // 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 (base.isNotNull())
+            newStart = base;
     }
 
     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.
-        }
+        if (extent.isNotNull())
+            newEnd = extent;
     }
 
-    newSelection.setIsDirectional(true);
+    VisibleSelection newSelection(newStart, newEnd, true /* isDirectional */);
+
+    if (!controller->selection().isRange())
+        m_webPage->updateSelectionScrollView(newSelection.visibleEnd().deepEquivalent().anchorNode());
 
     if (m_webPage->m_inputHandler->isInputMode()) {
         if (updateOrHandleInputSelection(newSelection, relativeStart, relativeEnd))
@@ -509,7 +548,7 @@ void SelectionHandler::setSelection(const WebCore::IntPoint& start, const WebCor
     }
 
     if (controller->selection() == newSelection) {
-        selectionPositionChanged();
+        selectionPositionChanged(true /* forceUpdateWithoutChange */);
         return;
     }
 
@@ -521,20 +560,23 @@ void SelectionHandler::setSelection(const WebCore::IntPoint& start, const WebCor
 
     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 {
+    if (unclippedRegion.isEmpty()) {
         // Requested selection results in an empty selection, skip this change.
-        selectionPositionChanged();
+        selectionPositionChanged(true /* forceUpdateWithoutChange */);
+
+        SelectionLog(Platform::LogLevelWarn, "SelectionHandler::setSelection selection points invalid, selection not updated.");
+        return;
+    }
 
-        DEBUG_SELECTION(LogLevelWarn, "SelectionHandler::setSelection selection points invalid, selection not updated\n");
+    // Check if the handles reversed position.
+    if (m_selectionActive && !newSelection.isBaseFirst()) {
+        m_webPage->m_client->notifySelectionHandlesReversed();
+        newSelection = VisibleSelection(newSelection.extent(), newSelection.base());
     }
+
+    controller->setSelection(newSelection);
+    SelectionLog(Platform::LogLevelInfo, "SelectionHandler::setSelection selection points valid, selection updated.");
 }
 
 // FIXME re-use this in context. Must be updated to include an option to return the href.
@@ -550,43 +592,253 @@ static Node* enclosingLinkEventParentForNode(Node* node)
     return linkNode && linkNode->isLink() ? linkNode : 0;
 }
 
-void SelectionHandler::selectAtPoint(const WebCore::IntPoint& location)
+TextGranularity textGranularityFromSelectionExpansionType(SelectionExpansionType selectionExpansionType)
 {
+    TextGranularity granularity;
+    switch (selectionExpansionType) {
+    case Word:
+    default:
+        granularity = WordGranularity;
+        break;
+    case Sentence:
+        granularity = SentenceGranularity;
+        break;
+    case Paragraph:
+        granularity = ParagraphGranularity;
+        break;
+    }
+    return granularity;
+}
+
+
+bool SelectionHandler::selectNodeIfFatFingersResultIsLink(FatFingersResult fatFingersResult)
+{
+    if (!fatFingersResult.isValid())
+        return false;
+    Node* targetNode = fatFingersResult.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)) {
+        Element* element = fatFingersResult.nodeAsElementIfApplicable();
+        if (!element)
+            return false;
+        m_animationHighlightColor = element->renderStyle()->initialTapHighlightColor();
+
+        selectObject(link);
+        // If selected object is a link, no need to wait for further expansion.
+        m_webPage->m_client->stopExpandingSelection();
+        return true;
+    }
+    return false;
+}
+
+WebCore::IntRect SelectionHandler::startCaretViewportRect(const WebCore::IntPoint& frameOffset) const
+{
+    WebCore::IntRect caretRect;
+    Frame* frame = m_webPage->focusedOrMainFrame();
+    if (!frame)
+        return caretRect;
+
+    if (frame->selection()->selectionType() != VisibleSelection::NoSelection) {
+        caretRect = frame->selection()->selection().visibleStart().absoluteCaretBounds();
+        caretRect.moveBy(frameOffset);
+    }
+
+    return caretRect;
+}
+
+void SelectionHandler::selectAtPoint(const WebCore::IntPoint& location, SelectionExpansionType selectionExpansionType)
+{
+    if (selectionExpansionType == Word) {
+        m_animationOverlayStartPos = VisiblePosition();
+        m_animationOverlayEndPos = VisiblePosition();
+        m_currentAnimationOverlayRegion = IntRectRegion();
+        m_nextAnimationOverlayRegion = IntRectRegion();
+        m_selectionSubframeViewportRect = WebCore::IntRect();
+    }
+
     // 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);
+    FatFingersResult fatFingersResult = m_webPage->m_touchEventHandler->lastFatFingersResult();
+    if (selectNodeIfFatFingersResultIsLink(fatFingersResult))
+        return;
+    if (!fatFingersResult.resultMatches(location, FatFingers::Text) || !fatFingersResult.positionWasAdjusted() || !fatFingersResult.nodeAsElementIfApplicable())
+        fatFingersResult = FatFingers(m_webPage, location, FatFingers::Text).findBestPoint();
+
+    if (!fatFingersResult.positionWasAdjusted()) {
+        if (isSelectionActive())
+            cancelSelection();
+        m_webPage->m_client->notifySelectionDetailsChanged(SelectionDetails());
+        m_webPage->m_touchEventHandler->sendClickAtFatFingersPoint();
+        return;
     }
 
-    ASSERT(targetNode);
+    targetPosition = fatFingersResult.adjustedPosition();
+    if (selectNodeIfFatFingersResultIsLink(fatFingersResult))
+        return;
 
-    // If the node at the point is a link, focus on the entire link, not a word.
-    if (Node* link = enclosingLinkEventParentForNode(targetNode)) {
-        selectObject(link);
+    selectObject(targetPosition, textGranularityFromSelectionExpansionType(selectionExpansionType));
+}
+
+static bool isInvalidParagraph(const VisiblePosition& pos)
+{
+    return endOfParagraph(pos).isNull() || pos == endOfParagraph(pos);
+}
+
+void SelectionHandler::selectNextParagraph()
+{
+    FrameSelection* controller = m_webPage->focusedOrMainFrame()->selection();
+
+    VisiblePosition startPos = VisiblePosition(controller->start(), controller->affinity());
+    if (isStartOfLine(startPos) && isEndOfDocument(startPos))
+        startPos = startPos.previous(CannotCrossEditingBoundary);
+
+    // Find next paragraph end position.
+    VisiblePosition endPos(controller->end(), controller->affinity()); // endPos here indicates the end of current paragraph
+    endPos = endPos.next(CannotCrossEditingBoundary); // find the start of next paragraph
+    while (!isEndOfDocument(endPos) && endPos.isNotNull() && isInvalidParagraph(endPos))
+        endPos = endPos.next(CannotCrossEditingBoundary); // go to next position
+    endPos = endOfParagraph(endPos); // find the end of paragraph
+
+    // Set selection if the paragraph is covered by overlay and endPos is not null.
+    if (m_currentAnimationOverlayRegion.extents().bottom() >= endPos.absoluteCaretBounds().maxY() && endPos.isNotNull()) {
+        VisibleSelection selection = VisibleSelection(startPos, endPos);
+        selection.setAffinity(controller->affinity());
+        controller->setSelection(selection);
+
+        // Stop expansion if reaching the end of page.
+        if (isEndOfDocument(endPos))
+            m_webPage->m_client->stopExpandingSelection();
+    }
+}
+
+void SelectionHandler::drawAnimationOverlay(IntRectRegion overlayRegion, bool isExpandingOverlayAtConstantRate, bool isStartOfSelection)
+{
+    if (isExpandingOverlayAtConstantRate) {
+        // When overlay expands at a constant rate, the current overlay height increases
+        // m_overlayExpansionHeight each time and the width is always same as next overlay region.
+        WebCore::IntRect currentOverlayRect = m_currentAnimationOverlayRegion.extents();
+        WebCore::IntRect nextOverlayRect = m_nextAnimationOverlayRegion.extents();
+        WebCore::IntRect overlayRect(WebCore::IntRect(nextOverlayRect.location(), WebCore::IntSize(nextOverlayRect.width(), currentOverlayRect.height() + m_overlayExpansionHeight)));
+        overlayRegion = IntRectRegion(overlayRect);
+    }
+
+    m_webPage->m_selectionHighlight->draw(overlayRegion,
+        m_animationHighlightColor.red(), m_animationHighlightColor.green(), m_animationHighlightColor.blue(), m_animationHighlightColor.alpha(),
+        false /* do not hide after scroll */,
+        isStartOfSelection);
+    m_currentAnimationOverlayRegion = overlayRegion;
+}
+
+IntRectRegion SelectionHandler::regionForSelectionQuads(VisibleSelection selection)
+{
+    Vector<FloatQuad> quads;
+    DOMSupport::visibleTextQuads(selection, quads);
+    IntRectRegion region;
+    regionForTextQuads(quads, region);
+    return region;
+}
+
+bool SelectionHandler::findNextAnimationOverlayRegion()
+{
+    // If overlay is at the end of document, stop overlay expansion.
+    if (isEndOfDocument(m_animationOverlayEndPos) || m_animationOverlayEndPos.isNull())
+        return false;
+
+    m_animationOverlayEndPos = m_animationOverlayEndPos.next(CannotCrossEditingBoundary);
+    while (!isEndOfDocument(m_animationOverlayEndPos) && m_animationOverlayEndPos.isNotNull() && isInvalidParagraph(m_animationOverlayEndPos))
+        m_animationOverlayEndPos = m_animationOverlayEndPos.next(CannotCrossEditingBoundary); // go to next position
+    m_animationOverlayEndPos = endOfParagraph(m_animationOverlayEndPos); // find end of paragraph
+
+    VisibleSelection selection(m_animationOverlayStartPos, m_animationOverlayEndPos);
+    m_nextAnimationOverlayRegion = regionForSelectionQuads(selection);
+    return true;
+}
+
+void SelectionHandler::expandSelection(bool isScrollStarted)
+{
+    if (m_currentAnimationOverlayRegion.isEmpty() || m_nextAnimationOverlayRegion.isEmpty())
+        return;
+    WebCore::IntPoint nextOverlayBottomRightPoint = WebCore::IntPoint(m_currentAnimationOverlayRegion.extents().bottomRight()) + WebCore::IntPoint(0, m_overlayExpansionHeight);
+    if (nextOverlayBottomRightPoint.y() > m_nextAnimationOverlayRegion.extents().bottom())
+        // Find next overlay region so that we can update overlay region's width while expanding.
+        if (!findNextAnimationOverlayRegion()) {
+            drawAnimationOverlay(m_nextAnimationOverlayRegion, false);
+            selectNextParagraph();
+            return;
+        }
+
+    // Draw overlay if the position is in the viewport and is not null.
+    // Otherwise, start scrolling if it hasn't started.
+    if (ensureSelectedTextVisible(nextOverlayBottomRightPoint, false /* do not scroll */) && m_animationOverlayEndPos.isNotNull())
+        drawAnimationOverlay(IntRectRegion(), true /* isExpandingOverlayAtConstantRate */);
+    else if (!isScrollStarted) {
+        m_webPage->m_client->startSelectionScroll();
         return;
     }
 
-    // selectAtPoint API currently only supports WordGranularity but may be extended in the future.
-    selectObject(targetPosition, WordGranularity);
+    selectNextParagraph();
+}
+
+bool SelectionHandler::ensureSelectedTextVisible(const WebCore::IntPoint& point, bool scrollIfNeeded)
+{
+    WebCore::IntRect viewportRect = selectionViewportRect();
+    if (!scrollIfNeeded)
+        // If reaching the bottom of content, ignore scroll margin so the text on the bottom can be selected.
+        return viewportRect.maxY() >= m_webPage->contentsSize().height() ? viewportRect.maxY() >= point.y() : viewportRect.maxY() >= point.y() + m_scrollMargin.height();
+
+    // Scroll position adjustment here is based on main frame. If selecting in a subframe, don't do animation.
+    if (!m_selectionSubframeViewportRect.isEmpty())
+        return false;
+
+    WebCore::IntRect endLocation = m_animationOverlayEndPos.absoluteCaretBounds();
+
+    Frame* focusedFrame = m_webPage->focusedOrMainFrame();
+    Frame* mainFrame = m_webPage->mainFrame();
+    // If we are selecting within an iframe, translate coordinates to main frame.
+    if (focusedFrame && focusedFrame->view() && mainFrame && mainFrame->view() && focusedFrame != mainFrame)
+        endLocation = mainFrame->view()->windowToContents(focusedFrame->view()->contentsToWindow(endLocation));
+
+    Node* anchorNode = m_animationOverlayEndPos.deepEquivalent().anchorNode();
+    if (!anchorNode || !anchorNode->renderer())
+        return false;
+
+    RenderLayer* layer = anchorNode->renderer()->enclosingLayer();
+    if (!layer)
+        return false;
+
+    endLocation.inflateX(m_scrollMargin.width());
+    endLocation.inflateY(m_scrollMargin.height());
+
+    WebCore::IntRect revealRect(layer->getRectToExpose(viewportRect, endLocation, ScrollAlignment::alignToEdgeIfNeeded, ScrollAlignment::alignToEdgeIfNeeded));
+    revealRect.setX(std::min(std::max(revealRect.x(), 0), m_webPage->maximumScrollPosition().x()));
+    revealRect.setY(std::min(std::max(revealRect.y(), 0), m_webPage->maximumScrollPosition().y()));
+
+    // Animate scroll position to revealRect.
+    m_webPage->animateToScaleAndDocumentScrollPosition(m_webPage->currentScale() /* Don't zoom */, WebCore::FloatPoint(revealRect.x(), revealRect.y()));
+    return true;
+}
+
+WebCore::IntRect SelectionHandler::selectionViewportRect() const
+{
+    if (m_selectionSubframeViewportRect.isEmpty())
+        return WebCore::IntRect(m_webPage->scrollPosition(), m_selectionViewportSize);
+    return m_selectionSubframeViewportRect;
+}
+
+void SelectionHandler::setParagraphExpansionScrollMargin(const WebCore::IntSize& scrollMargin)
+{
+    m_scrollMargin.setWidth(scrollMargin.width());
+    m_scrollMargin.setHeight(scrollMargin.height());
 }
 
-static bool expandSelectionToGranularity(Frame* frame, VisibleSelection selection, TextGranularity granularity, bool isInputMode)
+bool SelectionHandler::expandSelectionToGranularity(Frame* frame, VisibleSelection selection, TextGranularity granularity, bool isInputMode)
 {
     ASSERT(frame);
     ASSERT(frame->selection());
@@ -598,14 +850,29 @@ static bool expandSelectionToGranularity(Frame* frame, VisibleSelection selectio
         selection = DOMSupport::visibleSelectionForClosestActualWordStart(selection);
 
     selection.expandUsingGranularity(granularity);
-    RefPtr<Range> newRange = selection.toNormalizedRange();
-    RefPtr<Range> oldRange = frame->selection()->selection().toNormalizedRange();
-    EAffinity affinity = frame->selection()->affinity();
+    selection.setAffinity(frame->selection()->affinity());
 
-    if (isInputMode && !frame->editor()->client()->shouldChangeSelectedRange(oldRange.get(), newRange.get(), affinity, false))
+    if (isInputMode && !frame->selection()->shouldChangeSelection(selection))
         return false;
 
-    return frame->selection()->setSelectedRange(newRange.get(), affinity, true);
+    m_animationOverlayStartPos = selection.visibleStart();
+    m_animationOverlayEndPos = selection.visibleEnd();
+
+    if (granularity == WordGranularity) {
+        m_webPage->updateSelectionScrollView(selection.visibleEnd().deepEquivalent().anchorNode());
+
+        Element* element = m_animationOverlayStartPos.deepEquivalent().element();
+        if (!element)
+            return false;
+        m_animationHighlightColor = element->renderStyle()->initialTapHighlightColor();
+    }
+
+    ensureSelectedTextVisible(WebCore::IntPoint(), true /* scroll if needed */);
+    drawAnimationOverlay(regionForSelectionQuads(selection), false /* isExpandingOverlayAtConstantRate */, granularity == WordGranularity /* isStartOfSelection */);
+    frame->selection()->setSelection(selection);
+    if (granularity == ParagraphGranularity)
+        findNextAnimationOverlayRegion();
+    return true;
 }
 
 void SelectionHandler::selectObject(const WebCore::IntPoint& location, TextGranularity granularity)
@@ -614,12 +881,22 @@ void SelectionHandler::selectObject(const WebCore::IntPoint& location, TextGranu
     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());
+    SelectionLog(Platform::LogLevelInfo,
+        "SelectionHandler::selectObject adjusted points %s",
+        Platform::IntPoint(location).toString().c_str());
 
     WebCore::IntPoint relativePoint = DOMSupport::convertPointToFrame(m_webPage->mainFrame(), focusedFrame, location);
+
     VisiblePosition pointLocation(focusedFrame->visiblePositionForPoint(relativePoint));
     VisibleSelection selection = VisibleSelection(pointLocation, pointLocation);
 
+    // Move focus to the new node if we're not selecting in old input field.
+    if (!m_webPage->m_inputHandler->boundingBoxForInputField().contains(relativePoint)) {
+        Node* anchorNode = selection.start().anchorNode();
+        if (!anchorNode || anchorNode->isElementNode())
+            m_webPage->m_page->focusController()->setFocusedElement(toElement(anchorNode), focusedFrame);
+    }
+
     m_selectionActive = expandSelectionToGranularity(focusedFrame, selection, granularity, m_webPage->m_inputHandler->isInputMode());
 }
 
@@ -633,11 +910,18 @@ void SelectionHandler::selectObject(TextGranularity granularity)
     ASSERT(m_webPage->focusedOrMainFrame() && m_webPage->focusedOrMainFrame()->selection());
     Frame* focusedFrame = m_webPage->focusedOrMainFrame();
 
-    DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::selectObject using current selection");
+    SelectionLog(Platform::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 */);
+
+    // Use the current selection as the selection point.
+    VisibleSelection selectionOrigin = focusedFrame->selection()->selection();
+
+    // If this is the end of the input field, make sure we select the last word.
+    if (m_webPage->m_inputHandler->isCaretAtEndOfText())
+        selectionOrigin = previousWordPosition(selectionOrigin.start());
+
+    m_selectionActive = expandSelectionToGranularity(focusedFrame, selectionOrigin, granularity, true /* isInputMode */);
 }
 
 void SelectionHandler::selectObject(Node* node)
@@ -645,15 +929,21 @@ void SelectionHandler::selectObject(Node* node)
     if (!node)
         return;
 
+    // Clear input focus if we're not selecting text there.
+    if (node != m_webPage->m_inputHandler->currentFocusElement().get())
+        m_webPage->clearFocusNode();
+
     m_selectionActive = true;
 
     ASSERT(m_webPage && m_webPage->focusedOrMainFrame() && m_webPage->focusedOrMainFrame()->selection());
     Frame* focusedFrame = m_webPage->focusedOrMainFrame();
 
-    DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::selectNode");
+    SelectionLog(Platform::LogLevelInfo, "SelectionHandler::selectNode");
 
     VisibleSelection selection = VisibleSelection::selectionFromContentsOfNode(node);
+    drawAnimationOverlay(regionForSelectionQuads(selection), false /* isExpandingOverlayAtConstantRate */, true /* isStartOfSelection */);
     focusedFrame->selection()->setSelection(selection);
+    m_webPage->updateSelectionScrollView(node);
 }
 
 static TextDirection directionOfEnclosingBlock(FrameSelection* selection)
@@ -719,12 +1009,8 @@ static inline WebCore::IntPoint caretComparisonPointForRect(const WebCore::IntRe
     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)
+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)
@@ -732,6 +1018,8 @@ static void adjustCaretRects(WebCore::IntRect& startCaret, bool isStartCaretClip
     else {
         startCaret = rectList[0];
         startCaret.setLocation(caretLocationForRect(startCaret, true, isRTL));
+        // Reset width to 1 as we are strictly interested in caret location.
+        startCaret.setWidth(1);
     }
 
     if (isEndCaretClippedOut)
@@ -739,33 +1027,31 @@ static void adjustCaretRects(WebCore::IntRect& startCaret, bool isStartCaretClip
     else {
         endCaret = rectList[0];
         endCaret.setLocation(caretLocationForRect(endCaret, false, isRTL));
+        // Reset width to 1 as we are strictly interested in caret location.
+        endCaret.setWidth(1);
     }
 
     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());
+            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());
+            caretComparisonPointForRect(currentRect, false, isRTL),
+            caretComparisonPointForRect(endCaret, false, isRTL),
+            endReferencePoint, !isRTL) > 0) {
+                endCaret.setLocation(caretLocationForRect(currentRect, false, isRTL));
+                endCaret.setHeight(currentRect.height());
         }
     }
 }
@@ -778,11 +1064,11 @@ WebCore::IntPoint SelectionHandler::clipPointToVisibleContainer(const WebCore::I
     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()));
+        && 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;
@@ -795,34 +1081,56 @@ static WebCore::IntPoint referencePoint(const VisiblePosition& position, const W
     // entire region (which is already in frame coordinates so doesn't need
     // adjusting).
     WebCore::IntRect startCaretBounds(position.absoluteCaretBounds());
-    if (startCaretBounds.isEmpty())
+    startCaretBounds.move(framePosition.x(), framePosition.y());
+    if (startCaretBounds.isEmpty() || !boundingRect.contains(startCaretBounds))
         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(IntRectRegion& region, WebCore::IntPoint point)
+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++) {
+    for (unsigned 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 = toElement(focusedNode);
+    return DOMSupport::elementAttributeState(element, selectionTouchOverrideAttr) == DOMSupport::On;
+}
+
+RequestedHandlePosition SelectionHandler::requestedSelectionHandlePosition(const VisibleSelection& selection) const
+{
+    Element* element = DOMSupport::selectionContainerElement(selection);
+    return DOMSupport::elementHandlePositionAttribute(element);
+}
+
 // Note: This is the only function in SelectionHandler in which the coordinate
 // system is not entirely WebKit.
-void SelectionHandler::selectionPositionChanged(bool visualChangeOnly)
+void SelectionHandler::selectionPositionChanged(bool forceUpdateWithoutChange)
 {
-    DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::selectionPositionChanged visibleChangeOnly = %s", visualChangeOnly ? "true" : "false");
+    SelectionLog(Platform::LogLevelInfo,
+        "SelectionHandler::selectionPositionChanged forceUpdateWithoutChange = %s",
+        forceUpdateWithoutChange ? "true" : "false");
 
     // This method can get called during WebPage shutdown process.
     // If that is the case, just bail out since the client is not
@@ -831,18 +1139,25 @@ void SelectionHandler::selectionPositionChanged(bool visualChangeOnly)
         return;
 
     if (m_webPage->m_inputHandler->isInputMode() && m_webPage->m_inputHandler->processingChange()) {
+        if (m_webPage->m_selectionOverlay)
+            m_webPage->m_selectionOverlay->hide();
         m_webPage->m_client->cancelSelectionVisuals();
+
+        // Since we're not calling notifyCaretPositionChangedIfNeeded now, we have to do so at the end of processing
+        // to avoid dropping a notification.
+        m_didSuppressCaretPositionChangedNotification = true;
         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();
-    }
+    notifyCaretPositionChangedIfNeeded(m_webPage->m_touchEventHandler->m_userTriggeredTouchPressOnTextInput);
 
     // Enter selection mode if selection type is RangeSelection, and disable selection if
     // selection is active and becomes caret selection.
     Frame* frame = m_webPage->focusedOrMainFrame();
+
+    if (frame->view()->needsLayout())
+        return;
+
     WebCore::IntPoint framePos = m_webPage->frameOffset(frame);
     if (m_selectionActive && (m_caretActive || frame->selection()->isNone()))
         m_selectionActive = false;
@@ -851,8 +1166,19 @@ void SelectionHandler::selectionPositionChanged(bool visualChangeOnly)
     else if (!m_selectionActive)
         return;
 
-    WebCore::IntRect startCaret;
-    WebCore::IntRect endCaret;
+    if (Node* focusedNode = frame->document()->focusedNode()) {
+        if (focusedNode->hasTagName(HTMLNames::selectTag) || (focusedNode->isElementNode() && DOMSupport::isPopupInputField(toElement(focusedNode)))) {
+            SelectionLog(Platform::LogLevelInfo, "SelectionHandler::selectionPositionChanged selection is on a popup control, skipping rendering.");
+            return;
+        }
+    }
+
+    SelectionTimingLog(Platform::LogLevelInfo,
+        "SelectionHandler::selectionPositionChanged starting at %f",
+        m_timer.elapsed());
+
+    WebCore::IntRect startCaret(DOMSupport::InvalidPoint, WebCore::IntSize());
+    WebCore::IntRect endCaret(DOMSupport::InvalidPoint, WebCore::IntSize());
 
     // Get the text rects from the selections range.
     Vector<FloatQuad> quads;
@@ -863,18 +1189,17 @@ void SelectionHandler::selectionPositionChanged(bool visualChangeOnly)
 
     // 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))
+    if (!forceUpdateWithoutChange && m_lastSelectionRegion.isEqual(unclippedRegion))
         return;
 
     m_lastSelectionRegion = unclippedRegion;
+    bool isRTL = directionOfEnclosingBlock(frame->selection()) == RTL;
 
     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);
 
@@ -882,11 +1207,19 @@ void SelectionHandler::selectionPositionChanged(bool visualChangeOnly)
 
         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());
+#if SHOWDEBUG_SELECTIONHANDLER // Don't rely just on SelectionLog to avoid loop.
+        for (unsigned i = 0; i < unclippedRegion.numRects(); i++) {
+            SelectionLog(Platform::LogLevelInfo,
+                "Rect list - Unmodified #%d, %s",
+                i,
+                unclippedRegion.rects()[i].toString().c_str());
+        }
+        for (unsigned i = 0; i < visibleSelectionRegion.numRects(); i++) {
+            SelectionLog(Platform::LogLevelInfo,
+                "Rect list  - Clipped to Visible #%d, %s",
+                i,
+                visibleSelectionRegion.rects()[i].toString().c_str());
+        }
 #endif
 
         bool shouldCareAboutPossibleClippedOutSelection = frame != m_webPage->mainFrame() || m_webPage->m_inputHandler->isInputMode();
@@ -901,38 +1234,56 @@ void SelectionHandler::selectionPositionChanged(bool visualChangeOnly)
 
             // 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());
+    if (!frame->selection()->selection().isBaseFirst()) {
+        // End handle comes before start, invert the caret reference points.
+        WebCore::IntRect tmpCaret(startCaret);
+        startCaret = endCaret;
+        endCaret = tmpCaret;
+    }
+
+    SelectionLog(Platform::LogLevelInfo,
+        "SelectionHandler::selectionPositionChanged Start Rect=%s End Rect=%s",
+        Platform::IntRect(startCaret).toString().c_str(),
+        Platform::IntRect(endCaret).toString().c_str());
+
+    if (m_webPage->m_selectionOverlay)
+        m_webPage->m_selectionOverlay->draw(visibleSelectionRegion);
+
+    VisibleSelection currentSelection = frame->selection()->selection();
+    SelectionDetails details(startCaret, endCaret, visibleSelectionRegion, inputNodeOverridesTouch(),
+        m_lastSelection != currentSelection, requestedSelectionHandlePosition(frame->selection()->selection()), isRTL);
+
+    m_webPage->m_client->notifySelectionDetailsChanged(details);
+    m_lastSelection = currentSelection;
+    SelectionTimingLog(Platform::LogLevelInfo,
+        "SelectionHandler::selectionPositionChanged completed at %f",
+        m_timer.elapsed());
+}
+
 
+void SelectionHandler::notifyCaretPositionChangedIfNeeded(bool userTouchTriggeredOnTextField)
+{
+    m_didSuppressCaretPositionChangedNotification = false;
 
-    m_webPage->m_client->notifySelectionDetailsChanged(startCaret, endCaret, visibleSelectionRegion);
+    if (m_caretActive || (m_webPage->m_inputHandler->isInputMode() && m_webPage->focusedOrMainFrame()->selection()->isCaret())) {
+        // This may update the caret to no longer be active.
+        caretPositionChanged(userTouchTriggeredOnTextField);
+    }
 }
 
-// NOTE: This function is not in WebKit coordinates.
-void SelectionHandler::caretPositionChanged()
+void SelectionHandler::caretPositionChanged(bool userTouchTriggeredOnTextField)
 {
-    DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::caretPositionChanged");
+    SelectionLog(Platform::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 */);
+        m_webPage->m_client->notifyCaretChanged(caretLocation, userTouchTriggeredOnTextField);
         return;
     }
 
@@ -941,25 +1292,37 @@ void SelectionHandler::caretPositionChanged()
     // This function should only reach this point if input mode is active.
     ASSERT(m_webPage->m_inputHandler->isInputMode());
 
+    WebCore::IntRect clippingRectForContent(clippingRectForVisibleContent());
+    WebCore::IntPoint frameOffset(m_webPage->frameOffset(m_webPage->focusedOrMainFrame()));
     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());
+        caretLocation = startCaretViewportRect(frameOffset);
+        if (!caretLocation.isEmpty())
+            caretLocation.intersect(clippingRectForContent); // Clip against the containing frame and node boundaries.
     }
 
     m_caretActive = !caretLocation.isEmpty();
 
-    DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::caretPositionChanged caret Rect %d, %d, %dx%d",
-                        caretLocation.x(), caretLocation.y(), caretLocation.width(), caretLocation.height());
+    SelectionLog(Platform::LogLevelInfo,
+        "SelectionHandler::caretPositionChanged caret Rect %s",
+        Platform::IntRect(caretLocation).toString().c_str());
+
+    bool isSingleLineInput = m_caretActive && !m_webPage->m_inputHandler->isMultilineInputMode();
+    WebCore::IntRect nodeBoundingBox = isSingleLineInput ? m_webPage->m_inputHandler->boundingBoxForInputField() : WebCore::IntRect();
+
+    if (!nodeBoundingBox.isEmpty()) {
+        nodeBoundingBox.moveBy(frameOffset);
+
+        // Clip against the containing frame and node boundaries.
+        nodeBoundingBox.intersect(clippingRectForContent);
+    }
 
-    caretLocation = m_webPage->mapToTransformed(caretLocation);
-    m_webPage->clipToTransformedContentsRect(caretLocation);
+    SelectionLog(Platform::LogLevelInfo,
+        "SelectionHandler::caretPositionChanged: %s line input, single line bounding box %s%s",
+        isSingleLineInput ? "single" : "multi",
+        Platform::IntRect(nodeBoundingBox).toString().c_str(),
+        m_webPage->m_inputHandler->elementText().isEmpty() ? ", empty text field" : "");
 
-    m_webPage->m_client->notifyCaretChanged(caretLocation, m_webPage->m_touchEventHandler->lastFatFingersResult().isTextInput() /* userTouchTriggered */);
+    m_webPage->m_client->notifyCaretChanged(caretLocation, userTouchTriggeredOnTextField, isSingleLineInput, nodeBoundingBox, m_webPage->m_inputHandler->elementText().isEmpty());
 }
 
 bool SelectionHandler::selectionContains(const WebCore::IntPoint& point)