FocusController::setFocusedNode() should be setFocusedElement().
[WebKit-https.git] / Source / WebKit / blackberry / WebKitSupport / SelectionHandler.cpp
1 /*
2  * Copyright (C) 2010, 2011, 2012, 2013 Research In Motion Limited. All rights reserved.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17  */
18
19 #include "config.h"
20 #include "SelectionHandler.h"
21
22 #include "DOMSupport.h"
23 #include "Document.h"
24 #include "FatFingers.h"
25 #include "FloatQuad.h"
26 #include "FocusController.h"
27 #include "Frame.h"
28 #include "FrameSelection.h"
29 #include "FrameView.h"
30 #include "HitTestResult.h"
31 #include "InputHandler.h"
32 #include "IntRect.h"
33 #include "Page.h"
34 #include "RenderLayer.h"
35 #include "SelectionOverlay.h"
36 #include "TouchEventHandler.h"
37 #include "VisibleUnits.h"
38 #include "WebPageClient.h"
39 #include "WebPage_p.h"
40
41 #include "htmlediting.h"
42
43 #include <BlackBerryPlatformKeyboardEvent.h>
44 #include <BlackBerryPlatformLog.h>
45 #include <BlackBerryPlatformViewportAccessor.h>
46
47 #include <sys/keycodes.h>
48
49 // Note: This generates a lot of logs when dumping rects lists. It will seriously
50 // impact performance. Do not enable this during performance tests.
51 #define SHOWDEBUG_SELECTIONHANDLER 0
52 #define SHOWDEBUG_SELECTIONHANDLER_TIMING 0
53
54 using namespace BlackBerry::Platform;
55 using namespace WebCore;
56
57 #if SHOWDEBUG_SELECTIONHANDLER
58 #define SelectionLog(severity, format, ...) Platform::logAlways(severity, format, ## __VA_ARGS__)
59 #else
60 #define SelectionLog(severity, format, ...)
61 #endif // SHOWDEBUG_SELECTIONHANDLER
62
63 #if SHOWDEBUG_SELECTIONHANDLER_TIMING
64 #define SelectionTimingLog(severity, format, ...) Platform::logAlways(severity, format, ## __VA_ARGS__)
65 #else
66 #define SelectionTimingLog(severity, format, ...)
67 #endif // SHOWDEBUG_SELECTIONHANDLER_TIMING
68
69 namespace BlackBerry {
70 namespace WebKit {
71
72 SelectionHandler::SelectionHandler(WebPagePrivate* page)
73     : m_webPage(page)
74     , m_selectionActive(false)
75     , m_caretActive(false)
76     , m_lastUpdatedEndPointIsValid(false)
77     , m_didSuppressCaretPositionChangedNotification(false)
78 {
79 }
80
81 SelectionHandler::~SelectionHandler()
82 {
83 }
84
85 void SelectionHandler::cancelSelection()
86 {
87     m_selectionActive = false;
88     m_lastSelectionRegion = IntRectRegion();
89
90     if (m_webPage->m_selectionOverlay)
91         m_webPage->m_selectionOverlay->hide();
92     // Notify client with empty selection to ensure the handles are removed if
93     // rendering happened prior to processing on webkit thread
94     m_webPage->m_client->notifySelectionDetailsChanged(SelectionDetails());
95
96     m_webPage->updateSelectionScrollView(0);
97
98     SelectionLog(Platform::LogLevelInfo, "SelectionHandler::cancelSelection");
99
100     if (m_webPage->m_inputHandler->isInputMode())
101         m_webPage->m_inputHandler->cancelSelection();
102     else
103         m_webPage->focusedOrMainFrame()->selection()->clear();
104 }
105
106 BlackBerry::Platform::String SelectionHandler::selectedText() const
107 {
108     return m_webPage->focusedOrMainFrame()->editor().selectedText();
109 }
110
111 WebCore::IntRect SelectionHandler::clippingRectForVisibleContent() const
112 {
113     // Get the containing content rect for the frame.
114     Frame* frame = m_webPage->focusedOrMainFrame();
115     WebCore::IntRect clipRect = WebCore::IntRect(WebCore::IntPoint(0, 0), frame->view()->contentsSize());
116     if (frame != m_webPage->mainFrame()) {
117         clipRect = m_webPage->getRecursiveVisibleWindowRect(frame->view(), true /* no clip to main frame window */);
118         clipRect = m_webPage->m_mainFrame->view()->windowToContents(clipRect);
119     }
120
121     // Get the input field containing box.
122     WebCore::IntRect inputBoundingBox = m_webPage->m_inputHandler->boundingBoxForInputField();
123     if (!inputBoundingBox.isEmpty()) {
124         // Adjust the bounding box to the frame offset.
125         inputBoundingBox = m_webPage->mainFrame()->view()->windowToContents(frame->view()->contentsToWindow(inputBoundingBox));
126         clipRect.intersect(inputBoundingBox);
127     }
128     return clipRect;
129 }
130
131 void SelectionHandler::regionForTextQuads(Vector<FloatQuad> &quadList, IntRectRegion& region, bool shouldClipToVisibleContent) const
132 {
133     ASSERT(region.isEmpty());
134
135     if (!quadList.isEmpty()) {
136         FrameView* frameView = m_webPage->focusedOrMainFrame()->view();
137
138         // frameRect is in frame coordinates.
139         WebCore::IntRect frameRect(WebCore::IntPoint(0, 0), frameView->contentsSize());
140
141         // framePosition is in main frame coordinates.
142         WebCore::IntPoint framePosition = m_webPage->frameOffset(m_webPage->focusedOrMainFrame());
143
144         // Get the visibile content rect.
145         WebCore::IntRect clippingRect = shouldClipToVisibleContent ? clippingRectForVisibleContent() : WebCore::IntRect(-1, -1, 0, 0);
146
147         // Convert the text quads into a more platform friendy
148         // IntRectRegion and adjust for subframes.
149         Platform::IntRect selectionBoundingBox;
150         std::vector<Platform::IntRect> adjustedIntRects;
151         for (unsigned i = 0; i < quadList.size(); i++) {
152             WebCore::IntRect enclosingRect = quadList[i].enclosingBoundingBox();
153             enclosingRect.intersect(frameRect);
154             enclosingRect.move(framePosition.x(), framePosition.y());
155
156             // Clip to the visible content.
157             if (clippingRect.location() != DOMSupport::InvalidPoint)
158                 enclosingRect.intersect(clippingRect);
159
160             adjustedIntRects.push_back(enclosingRect);
161             selectionBoundingBox.unite(enclosingRect);
162         }
163         region = IntRectRegion(selectionBoundingBox, adjustedIntRects.size(), adjustedIntRects);
164     }
165 }
166
167 static VisiblePosition visiblePositionForPointIgnoringClipping(const Frame& frame, const WebCore::IntPoint& framePoint)
168 {
169     // Frame::visiblePositionAtPoint hard-codes ignoreClipping=false in the
170     // call to hitTestResultAtPoint. This has a bug where some pages (such as
171     // metafilter) will return the wrong VisiblePosition for points that are
172     // outside the visible rect. To work around the bug, this is a copy of
173     // visiblePositionAtPoint which which passes ignoreClipping=true.
174     // See RIM Bug #4315.
175     HitTestResult result = frame.eventHandler()->hitTestResultAtPoint(framePoint, HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::IgnoreClipping);
176
177     Node* node = result.innerNode();
178     if (!node || node->document() != frame.document())
179         return VisiblePosition();
180
181     RenderObject* renderer = node->renderer();
182     if (!renderer)
183         return VisiblePosition();
184
185     VisiblePosition visiblePos = renderer->positionForPoint(result.localPoint());
186     if (visiblePos.isNull())
187         visiblePos = VisiblePosition(Position(createLegacyEditingPosition(node, 0)));
188
189     return visiblePos;
190 }
191
192 static unsigned directionOfPointRelativeToRect(const WebCore::IntPoint& point, const WebCore::IntRect& rect, const bool useTopPadding = true, const bool useBottomPadding = true)
193 {
194     ASSERT(!rect.contains(point));
195
196     // Padding to prevent accidental trigger of up/down when intending to do horizontal movement.
197     const int verticalPadding = 5;
198
199     // Do height movement check first but add padding. We may be off on both x & y axis and only
200     // want to move in one direction at a time.
201     if (point.y() - (useTopPadding ? verticalPadding : 0) < rect.y())
202         return KEYCODE_UP;
203     if (point.y() > rect.maxY() + (useBottomPadding ? verticalPadding : 0))
204         return KEYCODE_DOWN;
205     if (point.x() < rect.location().x())
206         return KEYCODE_LEFT;
207     if (point.x() > rect.maxX())
208         return KEYCODE_RIGHT;
209
210     return 0;
211 }
212
213 bool SelectionHandler::shouldUpdateSelectionOrCaretForPoint(const WebCore::IntPoint& point, const WebCore::IntRect& caretRect, bool startCaret) const
214 {
215     ASSERT(m_webPage->m_inputHandler->isInputMode());
216
217     // If the point isn't valid don't block change as it is not actually changing.
218     if (point == DOMSupport::InvalidPoint)
219         return true;
220
221     VisibleSelection currentSelection = m_webPage->focusedOrMainFrame()->selection()->selection();
222
223     // If the input field is single line or we are on the first or last
224     // line of a multiline input field only horizontal movement is supported.
225     bool aboveCaret = point.y() < caretRect.y();
226     bool belowCaret = point.y() >= caretRect.maxY();
227
228     SelectionLog(Platform::LogLevelInfo,
229         "SelectionHandler::shouldUpdateSelectionOrCaretForPoint multiline = %s above = %s below = %s first line = %s last line = %s start = %s",
230         m_webPage->m_inputHandler->isMultilineInputMode() ? "true" : "false",
231         aboveCaret ? "true" : "false",
232         belowCaret ? "true" : "false",
233         inSameLine(currentSelection.visibleStart(), startOfEditableContent(currentSelection.visibleStart())) ? "true" : "false",
234         inSameLine(currentSelection.visibleEnd(), endOfEditableContent(currentSelection.visibleEnd())) ? "true" : "false",
235         startCaret ? "true" : "false");
236
237     if (!m_webPage->m_inputHandler->isMultilineInputMode() && (aboveCaret || belowCaret))
238         return false;
239     if (startCaret && inSameLine(currentSelection.visibleStart(), startOfEditableContent(currentSelection.visibleStart())) && aboveCaret)
240         return false;
241     if (!startCaret && inSameLine(currentSelection.visibleEnd(), endOfEditableContent(currentSelection.visibleEnd())) && belowCaret)
242         return false;
243
244     return true;
245 }
246
247 void SelectionHandler::setCaretPosition(const WebCore::IntPoint& position)
248 {
249     if (!m_webPage->m_inputHandler->isInputMode() || !m_webPage->focusedOrMainFrame()->document()->focusedNode())
250         return;
251
252     m_caretActive = true;
253
254     SelectionLog(Platform::LogLevelInfo,
255         "SelectionHandler::setCaretPosition requested point %s",
256         Platform::IntPoint(position).toString().c_str());
257
258     Frame* focusedFrame = m_webPage->focusedOrMainFrame();
259     FrameSelection* controller = focusedFrame->selection();
260     WebCore::IntPoint relativePoint = DOMSupport::convertPointToFrame(m_webPage->mainFrame(), focusedFrame, position);
261     WebCore::IntRect currentCaretRect = controller->selection().visibleStart().absoluteCaretBounds();
262
263     if (relativePoint == DOMSupport::InvalidPoint || !shouldUpdateSelectionOrCaretForPoint(relativePoint, currentCaretRect)) {
264         selectionPositionChanged(true /* forceUpdateWithoutChange */);
265         return;
266     }
267
268     WebCore::IntRect nodeOutlineBounds(m_webPage->m_inputHandler->boundingBoxForInputField());
269     if (!nodeOutlineBounds.isEmpty() && !nodeOutlineBounds.contains(relativePoint)) {
270         if (unsigned character = directionOfPointRelativeToRect(relativePoint, currentCaretRect))
271             m_webPage->m_inputHandler->handleKeyboardInput(Platform::KeyboardEvent(character));
272
273         // Send the selection changed in case this does not trigger a selection change to
274         // ensure the caret position is accurate. This may be a duplicate event.
275         selectionPositionChanged(true /* forceUpdateWithoutChange */);
276         return;
277     }
278
279     VisibleSelection newSelection(focusedFrame->visiblePositionForPoint(relativePoint));
280     if (controller->selection() == newSelection) {
281         selectionPositionChanged(true /* forceUpdateWithoutChange */);
282         return;
283     }
284
285     controller->setSelection(newSelection);
286
287     SelectionLog(Platform::LogLevelInfo, "SelectionHandler::setCaretPosition point valid, cursor updated");
288 }
289
290 void SelectionHandler::inputHandlerDidFinishProcessingChange()
291 {
292     if (m_didSuppressCaretPositionChangedNotification)
293         notifyCaretPositionChangedIfNeeded(false);
294 }
295
296 // This function makes sure we are not reducing the selection to a caret selection.
297 static bool shouldExtendSelectionInDirection(const VisibleSelection& selection, unsigned character)
298 {
299     FrameSelection tempSelection;
300     tempSelection.setSelection(selection);
301     switch (character) {
302     case KEYCODE_LEFT:
303         tempSelection.modify(FrameSelection::AlterationExtend, DirectionLeft, CharacterGranularity);
304         break;
305     case KEYCODE_RIGHT:
306         tempSelection.modify(FrameSelection::AlterationExtend, DirectionRight, CharacterGranularity);
307         break;
308     case KEYCODE_UP:
309         tempSelection.modify(FrameSelection::AlterationExtend, DirectionBackward, LineGranularity);
310         break;
311     case KEYCODE_DOWN:
312         tempSelection.modify(FrameSelection::AlterationExtend, DirectionForward, LineGranularity);
313         break;
314     default:
315         break;
316     }
317
318     if ((character == KEYCODE_LEFT || character == KEYCODE_RIGHT)
319         && (!inSameLine(selection.visibleStart(), tempSelection.selection().visibleStart())
320             || !inSameLine(selection.visibleEnd(), tempSelection.selection().visibleEnd())))
321         return false;
322
323     return tempSelection.selection().selectionType() == VisibleSelection::RangeSelection;
324 }
325
326 static int clamp(const int min, const int value, const int max)
327 {
328     return value < min ? min : std::min(value, max);
329 }
330
331 static VisiblePosition directionalVisiblePositionAtExtentOfBox(Frame* frame, const WebCore::IntRect& boundingBox, unsigned direction, const WebCore::IntPoint& basePoint)
332 {
333     ASSERT(frame);
334
335     if (!frame)
336         return VisiblePosition();
337
338     switch (direction) {
339     case KEYCODE_LEFT:
340         // Extend x to start and clamp y to the edge of bounding box.
341         return frame->visiblePositionForPoint(WebCore::IntPoint(boundingBox.x(), clamp(boundingBox.y(), basePoint.y(), boundingBox.maxY())));
342     case KEYCODE_RIGHT:
343         // Extend x to end and clamp y to the edge of bounding box.
344         return frame->visiblePositionForPoint(WebCore::IntPoint(boundingBox.maxX(), clamp(boundingBox.y(), basePoint.y(), boundingBox.maxY())));
345     case KEYCODE_UP:
346         // Extend y to top and clamp x to the edge of bounding box.
347         return frame->visiblePositionForPoint(WebCore::IntPoint(clamp(boundingBox.x(), basePoint.x(), boundingBox.maxX()), boundingBox.y()));
348     case KEYCODE_DOWN:
349         // Extend y to bottom and clamp x to the edge of bounding box.
350         return frame->visiblePositionForPoint(WebCore::IntPoint(clamp(boundingBox.x(), basePoint.x(), boundingBox.maxX()), boundingBox.maxY()));
351     default:
352         break;
353     }
354
355     return frame->visiblePositionForPoint(WebCore::IntPoint(basePoint.x(), basePoint.y()));
356 }
357
358 static bool pointIsOutsideOfBoundingBoxInDirection(unsigned direction, const WebCore::IntPoint& selectionPoint, const WebCore::IntRect& boundingBox)
359 {
360     if ((direction == KEYCODE_LEFT && selectionPoint.x() < boundingBox.x())
361         || (direction == KEYCODE_UP && selectionPoint.y() < boundingBox.y())
362         || (direction == KEYCODE_RIGHT && selectionPoint.x() > boundingBox.maxX())
363         || (direction == KEYCODE_DOWN && selectionPoint.y() > boundingBox.maxY()))
364         return true;
365
366     return false;
367 }
368
369 unsigned SelectionHandler::extendSelectionToFieldBoundary(bool isStartHandle, const WebCore::IntPoint& selectionPoint, VisibleSelection& newSelection)
370 {
371     Frame* focusedFrame = m_webPage->focusedOrMainFrame();
372     if (!focusedFrame->document()->focusedNode() || !focusedFrame->document()->focusedNode()->renderer())
373         return 0;
374
375     VisibleSelection activeSelection = focusedFrame->selection()->selection();
376
377     WebCore::IntRect caretRect = isStartHandle ? activeSelection.visibleStart().absoluteCaretBounds() : activeSelection.visibleEnd().absoluteCaretBounds();
378
379     WebCore::IntRect nodeBoundingBox = focusedFrame->document()->focusedNode()->renderer()->absoluteBoundingBoxRect();
380     nodeBoundingBox.inflate(-1);
381
382     // Start handle is outside of the field. Treat it as the changed handle and move
383     // relative to the start caret rect.
384     unsigned character = directionOfPointRelativeToRect(selectionPoint, caretRect, isStartHandle /* useTopPadding */, !isStartHandle /* useBottomPadding */);
385
386     // Prevent incorrect movement, handles can only extend the selection this way
387     // to prevent inversion of the handles.
388     if ((isStartHandle && (character == KEYCODE_RIGHT || character == KEYCODE_DOWN))
389         || (!isStartHandle && (character == KEYCODE_LEFT || character == KEYCODE_UP)))
390         character = 0;
391
392     VisiblePosition newVisiblePosition = isStartHandle ? activeSelection.extent() : activeSelection.base();
393     // Extend the selection to the bounds of the box before doing incremental scroll if the point is outside the node.
394     // Don't extend selection and handle the character at the same time.
395     if (pointIsOutsideOfBoundingBoxInDirection(character, selectionPoint, nodeBoundingBox))
396         newVisiblePosition = directionalVisiblePositionAtExtentOfBox(focusedFrame, nodeBoundingBox, character, selectionPoint);
397
398     if (isStartHandle)
399         newSelection = VisibleSelection(newVisiblePosition, newSelection.extent(), true /* isDirectional */);
400     else
401         newSelection = VisibleSelection(newSelection.base(), newVisiblePosition, true /* isDirectional */);
402
403     // If no selection will be changed, return the character to extend using navigation.
404     if (activeSelection == newSelection)
405         return character;
406
407     // Selection has been updated.
408     return 0;
409 }
410
411 // Returns true if handled.
412 bool SelectionHandler::updateOrHandleInputSelection(VisibleSelection& newSelection, const WebCore::IntPoint& relativeStart, const WebCore::IntPoint& relativeEnd)
413 {
414     ASSERT(m_webPage->m_inputHandler->isInputMode());
415
416     Frame* focusedFrame = m_webPage->focusedOrMainFrame();
417     Node* focusedNode = focusedFrame->document()->focusedNode();
418     if (!focusedNode || !focusedNode->renderer())
419         return false;
420
421     FrameSelection* controller = focusedFrame->selection();
422
423     WebCore::IntRect currentStartCaretRect = controller->selection().visibleStart().absoluteCaretBounds();
424     WebCore::IntRect currentEndCaretRect = controller->selection().visibleEnd().absoluteCaretBounds();
425
426     // Check if the handle movement is valid.
427     if (!shouldUpdateSelectionOrCaretForPoint(relativeStart, currentStartCaretRect, true /* startCaret */)
428         || !shouldUpdateSelectionOrCaretForPoint(relativeEnd, currentEndCaretRect, false /* startCaret */)) {
429         selectionPositionChanged(true /* forceUpdateWithoutChange */);
430         return true;
431     }
432
433     WebCore::IntRect nodeBoundingBox = focusedNode->renderer()->absoluteBoundingBoxRect();
434
435     // Only do special handling if one handle is outside of the node.
436     bool startIsOutsideOfField = relativeStart != DOMSupport::InvalidPoint && !nodeBoundingBox.contains(relativeStart);
437     bool endIsOutsideOfField = relativeEnd != DOMSupport::InvalidPoint && !nodeBoundingBox.contains(relativeEnd);
438     if (startIsOutsideOfField && endIsOutsideOfField)
439         return false;
440
441     unsigned character = 0;
442     bool needToInvertDirection = false;
443     if (startIsOutsideOfField) {
444         character = extendSelectionToFieldBoundary(true /* isStartHandle */, relativeStart, newSelection);
445         if (character && controller->selection().isBaseFirst()) {
446             // Invert the selection so that the cursor point is at the beginning.
447             controller->setSelection(VisibleSelection(controller->selection().end(), controller->selection().start(), true /* isDirectional */));
448             needToInvertDirection = true;
449         }
450     } else if (endIsOutsideOfField) {
451         character = extendSelectionToFieldBoundary(false /* isStartHandle */, relativeEnd, newSelection);
452         if (character && !controller->selection().isBaseFirst()) {
453             // Reset the selection so that the end is the edit point.
454             controller->setSelection(VisibleSelection(controller->selection().start(), controller->selection().end(), true /* isDirectional */));
455         }
456     }
457
458     if (!character)
459         return false;
460
461     SelectionLog(Platform::LogLevelInfo,
462         "SelectionHandler::updateOrHandleInputSelection making selection change attempt using key event %d",
463         character);
464
465     if (shouldExtendSelectionInDirection(controller->selection(), character))
466         m_webPage->m_inputHandler->handleKeyboardInput(Platform::KeyboardEvent(character, Platform::KeyboardEvent::KeyDown, KEYMOD_SHIFT));
467
468     if (needToInvertDirection)
469         controller->setSelection(VisibleSelection(controller->selection().extent(), controller->selection().base(), true /* isDirectional */));
470
471     // Send the selection changed in case this does not trigger a selection change to
472     // ensure the caret position is accurate. This may be a duplicate event.
473     selectionPositionChanged(true /* forceUpdateWithoutChange */);
474     return true;
475 }
476
477 void SelectionHandler::setSelection(WebCore::IntPoint start, WebCore::IntPoint end)
478 {
479     m_selectionActive = true;
480
481     ASSERT(m_webPage);
482     ASSERT(m_webPage->focusedOrMainFrame());
483     ASSERT(m_webPage->focusedOrMainFrame()->selection());
484
485     Frame* focusedFrame = m_webPage->focusedOrMainFrame();
486     FrameSelection* controller = focusedFrame->selection();
487
488 #if SHOWDEBUG_SELECTIONHANDLER_TIMING
489     m_timer.start();
490 #endif
491
492     SelectionLog(Platform::LogLevelInfo,
493         "SelectionHandler::setSelection adjusted points %s, %s",
494         Platform::IntPoint(start).toString().c_str(),
495         Platform::IntPoint(end).toString().c_str());
496
497     // Note that IntPoint(-1, -1) is being our sentinel so far for
498     // clipped out selection starting or ending location.
499     bool startIsValid = start != DOMSupport::InvalidPoint;
500     m_lastUpdatedEndPointIsValid = end != DOMSupport::InvalidPoint;
501
502     // At least one of the locations must be valid.
503     ASSERT(startIsValid || m_lastUpdatedEndPointIsValid);
504
505     if (m_webPage->m_inputHandler->isInputMode() && !m_webPage->m_inputHandler->isMultilineInputMode()) {
506         WebCore::IntRect caret(startCaretViewportRect(m_webPage->frameOffset(focusedFrame)));
507         if (!caret.isEmpty()) {
508             int centerOfCaretY = caret.center().y();
509             if (startIsValid)
510                 start.setY(centerOfCaretY);
511             if (m_lastUpdatedEndPointIsValid)
512                 end.setY(centerOfCaretY);
513         }
514     }
515
516     WebCore::IntPoint relativeStart = start;
517     WebCore::IntPoint relativeEnd = end;
518
519     // Initialize the new start and end of our selection at the current positions.
520     VisiblePosition newStart = controller->selection().visibleStart();
521     VisiblePosition newEnd = controller->selection().visibleEnd();
522
523     // We don't return early in the following, so that we can do input field scrolling if the
524     // handle is outside the bounds of the field. This can be extended to handle sub-region
525     // scrolling as well
526     if (startIsValid) {
527         relativeStart = DOMSupport::convertPointToFrame(m_webPage->mainFrame(), focusedFrame, start);
528         VisiblePosition base = visiblePositionForPointIgnoringClipping(*focusedFrame, clipPointToVisibleContainer(start));
529         if (base.isNotNull())
530             newStart = base;
531     }
532
533     if (m_lastUpdatedEndPointIsValid) {
534         relativeEnd = DOMSupport::convertPointToFrame(m_webPage->mainFrame(), focusedFrame, end);
535         VisiblePosition extent = visiblePositionForPointIgnoringClipping(*focusedFrame, clipPointToVisibleContainer(end));
536         if (extent.isNotNull())
537             newEnd = extent;
538     }
539
540     VisibleSelection newSelection(newStart, newEnd, true /* isDirectional */);
541
542     if (!controller->selection().isRange())
543         m_webPage->updateSelectionScrollView(newSelection.visibleEnd().deepEquivalent().anchorNode());
544
545     if (m_webPage->m_inputHandler->isInputMode()) {
546         if (updateOrHandleInputSelection(newSelection, relativeStart, relativeEnd))
547             return;
548     }
549
550     if (controller->selection() == newSelection) {
551         selectionPositionChanged(true /* forceUpdateWithoutChange */);
552         return;
553     }
554
555     // If the selection size is reduce to less than a character, selection type becomes
556     // Caret. As long as it is still a range, it's a valid selection. Selection cannot
557     // be cancelled through this function.
558     Vector<FloatQuad> quads;
559     DOMSupport::visibleTextQuads(newSelection, quads);
560
561     IntRectRegion unclippedRegion;
562     regionForTextQuads(quads, unclippedRegion, false /* shouldClipToVisibleContent */);
563
564     if (unclippedRegion.isEmpty()) {
565         // Requested selection results in an empty selection, skip this change.
566         selectionPositionChanged(true /* forceUpdateWithoutChange */);
567
568         SelectionLog(Platform::LogLevelWarn, "SelectionHandler::setSelection selection points invalid, selection not updated.");
569         return;
570     }
571
572     // Check if the handles reversed position.
573     if (m_selectionActive && !newSelection.isBaseFirst()) {
574         m_webPage->m_client->notifySelectionHandlesReversed();
575         newSelection = VisibleSelection(newSelection.extent(), newSelection.base());
576     }
577
578     controller->setSelection(newSelection);
579     SelectionLog(Platform::LogLevelInfo, "SelectionHandler::setSelection selection points valid, selection updated.");
580 }
581
582 // FIXME re-use this in context. Must be updated to include an option to return the href.
583 // This function should be moved to a new unit file. Names suggetions include DOMQueries
584 // and NodeTypes. Functions currently in InputHandler.cpp, SelectionHandler.cpp and WebPage.cpp
585 // can all be moved in.
586 static Node* enclosingLinkEventParentForNode(Node* node)
587 {
588     if (!node)
589         return 0;
590
591     Node* linkNode = node->enclosingLinkEventParentOrSelf();
592     return linkNode && linkNode->isLink() ? linkNode : 0;
593 }
594
595 TextGranularity textGranularityFromSelectionExpansionType(SelectionExpansionType selectionExpansionType)
596 {
597     TextGranularity granularity;
598     switch (selectionExpansionType) {
599     case Word:
600     default:
601         granularity = WordGranularity;
602         break;
603     case Sentence:
604         granularity = SentenceGranularity;
605         break;
606     case Paragraph:
607         granularity = ParagraphGranularity;
608         break;
609     }
610     return granularity;
611 }
612
613
614 bool SelectionHandler::selectNodeIfFatFingersResultIsLink(FatFingersResult fatFingersResult)
615 {
616     if (!fatFingersResult.isValid())
617         return false;
618     Node* targetNode = fatFingersResult.node(FatFingersResult::ShadowContentNotAllowed);
619     ASSERT(targetNode);
620     // If the node at the point is a link, focus on the entire link, not a word.
621     if (Node* link = enclosingLinkEventParentForNode(targetNode)) {
622         Element* element = fatFingersResult.nodeAsElementIfApplicable();
623         if (!element)
624             return false;
625         m_animationHighlightColor = element->renderStyle()->initialTapHighlightColor();
626
627         selectObject(link);
628         // If selected object is a link, no need to wait for further expansion.
629         m_webPage->m_client->stopExpandingSelection();
630         return true;
631     }
632     return false;
633 }
634
635 WebCore::IntRect SelectionHandler::startCaretViewportRect(const WebCore::IntPoint& frameOffset) const
636 {
637     WebCore::IntRect caretRect;
638     Frame* frame = m_webPage->focusedOrMainFrame();
639     if (!frame)
640         return caretRect;
641
642     if (frame->selection()->selectionType() != VisibleSelection::NoSelection) {
643         caretRect = frame->selection()->selection().visibleStart().absoluteCaretBounds();
644         caretRect.moveBy(frameOffset);
645     }
646
647     return caretRect;
648 }
649
650 void SelectionHandler::selectAtPoint(const WebCore::IntPoint& location, SelectionExpansionType selectionExpansionType)
651 {
652     if (selectionExpansionType == Word) {
653         m_animationOverlayStartPos = VisiblePosition();
654         m_animationOverlayEndPos = VisiblePosition();
655         m_currentAnimationOverlayRegion = IntRectRegion();
656         m_nextAnimationOverlayRegion = IntRectRegion();
657         m_selectionSubframeViewportRect = WebCore::IntRect();
658     }
659
660     // If point is invalid trigger selection based expansion.
661     if (location == DOMSupport::InvalidPoint) {
662         selectObject(WordGranularity);
663         return;
664     }
665
666     WebCore::IntPoint targetPosition;
667
668     FatFingersResult fatFingersResult = m_webPage->m_touchEventHandler->lastFatFingersResult();
669     if (selectNodeIfFatFingersResultIsLink(fatFingersResult))
670         return;
671     if (!fatFingersResult.resultMatches(location, FatFingers::Text) || !fatFingersResult.positionWasAdjusted() || !fatFingersResult.nodeAsElementIfApplicable())
672         fatFingersResult = FatFingers(m_webPage, location, FatFingers::Text).findBestPoint();
673
674     if (!fatFingersResult.positionWasAdjusted()) {
675         if (isSelectionActive())
676             cancelSelection();
677         m_webPage->m_client->notifySelectionDetailsChanged(SelectionDetails());
678         m_webPage->m_touchEventHandler->sendClickAtFatFingersPoint();
679         return;
680     }
681
682     targetPosition = fatFingersResult.adjustedPosition();
683     if (selectNodeIfFatFingersResultIsLink(fatFingersResult))
684         return;
685
686     selectObject(targetPosition, textGranularityFromSelectionExpansionType(selectionExpansionType));
687 }
688
689 static bool isInvalidParagraph(const VisiblePosition& pos)
690 {
691     return endOfParagraph(pos).isNull() || pos == endOfParagraph(pos);
692 }
693
694 void SelectionHandler::selectNextParagraph()
695 {
696     FrameSelection* controller = m_webPage->focusedOrMainFrame()->selection();
697
698     VisiblePosition startPos = VisiblePosition(controller->start(), controller->affinity());
699     if (isStartOfLine(startPos) && isEndOfDocument(startPos))
700         startPos = startPos.previous(CannotCrossEditingBoundary);
701
702     // Find next paragraph end position.
703     VisiblePosition endPos(controller->end(), controller->affinity()); // endPos here indicates the end of current paragraph
704     endPos = endPos.next(CannotCrossEditingBoundary); // find the start of next paragraph
705     while (!isEndOfDocument(endPos) && endPos.isNotNull() && isInvalidParagraph(endPos))
706         endPos = endPos.next(CannotCrossEditingBoundary); // go to next position
707     endPos = endOfParagraph(endPos); // find the end of paragraph
708
709     // Set selection if the paragraph is covered by overlay and endPos is not null.
710     if (m_currentAnimationOverlayRegion.extents().bottom() >= endPos.absoluteCaretBounds().maxY() && endPos.isNotNull()) {
711         VisibleSelection selection = VisibleSelection(startPos, endPos);
712         selection.setAffinity(controller->affinity());
713         controller->setSelection(selection);
714
715         // Stop expansion if reaching the end of page.
716         if (isEndOfDocument(endPos))
717             m_webPage->m_client->stopExpandingSelection();
718     }
719 }
720
721 void SelectionHandler::drawAnimationOverlay(IntRectRegion overlayRegion, bool isExpandingOverlayAtConstantRate, bool isStartOfSelection)
722 {
723     if (isExpandingOverlayAtConstantRate) {
724         // When overlay expands at a constant rate, the current overlay height increases
725         // m_overlayExpansionHeight each time and the width is always same as next overlay region.
726         WebCore::IntRect currentOverlayRect = m_currentAnimationOverlayRegion.extents();
727         WebCore::IntRect nextOverlayRect = m_nextAnimationOverlayRegion.extents();
728         WebCore::IntRect overlayRect(WebCore::IntRect(nextOverlayRect.location(), WebCore::IntSize(nextOverlayRect.width(), currentOverlayRect.height() + m_overlayExpansionHeight)));
729         overlayRegion = IntRectRegion(overlayRect);
730     }
731
732     m_webPage->m_selectionHighlight->draw(overlayRegion,
733         m_animationHighlightColor.red(), m_animationHighlightColor.green(), m_animationHighlightColor.blue(), m_animationHighlightColor.alpha(),
734         false /* do not hide after scroll */,
735         isStartOfSelection);
736     m_currentAnimationOverlayRegion = overlayRegion;
737 }
738
739 IntRectRegion SelectionHandler::regionForSelectionQuads(VisibleSelection selection)
740 {
741     Vector<FloatQuad> quads;
742     DOMSupport::visibleTextQuads(selection, quads);
743     IntRectRegion region;
744     regionForTextQuads(quads, region);
745     return region;
746 }
747
748 bool SelectionHandler::findNextAnimationOverlayRegion()
749 {
750     // If overlay is at the end of document, stop overlay expansion.
751     if (isEndOfDocument(m_animationOverlayEndPos) || m_animationOverlayEndPos.isNull())
752         return false;
753
754     m_animationOverlayEndPos = m_animationOverlayEndPos.next(CannotCrossEditingBoundary);
755     while (!isEndOfDocument(m_animationOverlayEndPos) && m_animationOverlayEndPos.isNotNull() && isInvalidParagraph(m_animationOverlayEndPos))
756         m_animationOverlayEndPos = m_animationOverlayEndPos.next(CannotCrossEditingBoundary); // go to next position
757     m_animationOverlayEndPos = endOfParagraph(m_animationOverlayEndPos); // find end of paragraph
758
759     VisibleSelection selection(m_animationOverlayStartPos, m_animationOverlayEndPos);
760     m_nextAnimationOverlayRegion = regionForSelectionQuads(selection);
761     return true;
762 }
763
764 void SelectionHandler::expandSelection(bool isScrollStarted)
765 {
766     if (m_currentAnimationOverlayRegion.isEmpty() || m_nextAnimationOverlayRegion.isEmpty())
767         return;
768     WebCore::IntPoint nextOverlayBottomRightPoint = WebCore::IntPoint(m_currentAnimationOverlayRegion.extents().bottomRight()) + WebCore::IntPoint(0, m_overlayExpansionHeight);
769     if (nextOverlayBottomRightPoint.y() > m_nextAnimationOverlayRegion.extents().bottom())
770         // Find next overlay region so that we can update overlay region's width while expanding.
771         if (!findNextAnimationOverlayRegion()) {
772             drawAnimationOverlay(m_nextAnimationOverlayRegion, false);
773             selectNextParagraph();
774             return;
775         }
776
777     // Draw overlay if the position is in the viewport and is not null.
778     // Otherwise, start scrolling if it hasn't started.
779     if (ensureSelectedTextVisible(nextOverlayBottomRightPoint, false /* do not scroll */) && m_animationOverlayEndPos.isNotNull())
780         drawAnimationOverlay(IntRectRegion(), true /* isExpandingOverlayAtConstantRate */);
781     else if (!isScrollStarted) {
782         m_webPage->m_client->startSelectionScroll();
783         return;
784     }
785
786     selectNextParagraph();
787 }
788
789 bool SelectionHandler::ensureSelectedTextVisible(const WebCore::IntPoint& point, bool scrollIfNeeded)
790 {
791     WebCore::IntRect viewportRect = selectionViewportRect();
792     if (!scrollIfNeeded)
793         // If reaching the bottom of content, ignore scroll margin so the text on the bottom can be selected.
794         return viewportRect.maxY() >= m_webPage->contentsSize().height() ? viewportRect.maxY() >= point.y() : viewportRect.maxY() >= point.y() + m_scrollMargin.height();
795
796     // Scroll position adjustment here is based on main frame. If selecting in a subframe, don't do animation.
797     if (!m_selectionSubframeViewportRect.isEmpty())
798         return false;
799
800     WebCore::IntRect endLocation = m_animationOverlayEndPos.absoluteCaretBounds();
801
802     Frame* focusedFrame = m_webPage->focusedOrMainFrame();
803     Frame* mainFrame = m_webPage->mainFrame();
804     // If we are selecting within an iframe, translate coordinates to main frame.
805     if (focusedFrame && focusedFrame->view() && mainFrame && mainFrame->view() && focusedFrame != mainFrame)
806         endLocation = mainFrame->view()->windowToContents(focusedFrame->view()->contentsToWindow(endLocation));
807
808     Node* anchorNode = m_animationOverlayEndPos.deepEquivalent().anchorNode();
809     if (!anchorNode || !anchorNode->renderer())
810         return false;
811
812     RenderLayer* layer = anchorNode->renderer()->enclosingLayer();
813     if (!layer)
814         return false;
815
816     endLocation.inflateX(m_scrollMargin.width());
817     endLocation.inflateY(m_scrollMargin.height());
818
819     WebCore::IntRect revealRect(layer->getRectToExpose(viewportRect, endLocation, ScrollAlignment::alignToEdgeIfNeeded, ScrollAlignment::alignToEdgeIfNeeded));
820     revealRect.setX(std::min(std::max(revealRect.x(), 0), m_webPage->maximumScrollPosition().x()));
821     revealRect.setY(std::min(std::max(revealRect.y(), 0), m_webPage->maximumScrollPosition().y()));
822
823     // Animate scroll position to revealRect.
824     m_webPage->animateToScaleAndDocumentScrollPosition(m_webPage->currentScale() /* Don't zoom */, WebCore::FloatPoint(revealRect.x(), revealRect.y()));
825     return true;
826 }
827
828 WebCore::IntRect SelectionHandler::selectionViewportRect() const
829 {
830     if (m_selectionSubframeViewportRect.isEmpty())
831         return WebCore::IntRect(m_webPage->scrollPosition(), m_selectionViewportSize);
832     return m_selectionSubframeViewportRect;
833 }
834
835 void SelectionHandler::setParagraphExpansionScrollMargin(const WebCore::IntSize& scrollMargin)
836 {
837     m_scrollMargin.setWidth(scrollMargin.width());
838     m_scrollMargin.setHeight(scrollMargin.height());
839 }
840
841 bool SelectionHandler::expandSelectionToGranularity(Frame* frame, VisibleSelection selection, TextGranularity granularity, bool isInputMode)
842 {
843     ASSERT(frame);
844     ASSERT(frame->selection());
845
846     if (!(selection.start().anchorNode() && selection.start().anchorNode()->isTextNode()))
847         return false;
848
849     if (granularity == WordGranularity)
850         selection = DOMSupport::visibleSelectionForClosestActualWordStart(selection);
851
852     selection.expandUsingGranularity(granularity);
853     selection.setAffinity(frame->selection()->affinity());
854
855     if (isInputMode && !frame->selection()->shouldChangeSelection(selection))
856         return false;
857
858     m_animationOverlayStartPos = selection.visibleStart();
859     m_animationOverlayEndPos = selection.visibleEnd();
860
861     if (granularity == WordGranularity) {
862         m_webPage->updateSelectionScrollView(selection.visibleEnd().deepEquivalent().anchorNode());
863
864         Element* element = m_animationOverlayStartPos.deepEquivalent().element();
865         if (!element)
866             return false;
867         m_animationHighlightColor = element->renderStyle()->initialTapHighlightColor();
868     }
869
870     ensureSelectedTextVisible(WebCore::IntPoint(), true /* scroll if needed */);
871     drawAnimationOverlay(regionForSelectionQuads(selection), false /* isExpandingOverlayAtConstantRate */, granularity == WordGranularity /* isStartOfSelection */);
872     frame->selection()->setSelection(selection);
873     if (granularity == ParagraphGranularity)
874         findNextAnimationOverlayRegion();
875     return true;
876 }
877
878 void SelectionHandler::selectObject(const WebCore::IntPoint& location, TextGranularity granularity)
879 {
880     ASSERT(location.x() >= 0 && location.y() >= 0);
881     ASSERT(m_webPage && m_webPage->focusedOrMainFrame() && m_webPage->focusedOrMainFrame()->selection());
882     Frame* focusedFrame = m_webPage->focusedOrMainFrame();
883
884     SelectionLog(Platform::LogLevelInfo,
885         "SelectionHandler::selectObject adjusted points %s",
886         Platform::IntPoint(location).toString().c_str());
887
888     WebCore::IntPoint relativePoint = DOMSupport::convertPointToFrame(m_webPage->mainFrame(), focusedFrame, location);
889
890     VisiblePosition pointLocation(focusedFrame->visiblePositionForPoint(relativePoint));
891     VisibleSelection selection = VisibleSelection(pointLocation, pointLocation);
892
893     // Move focus to the new node if we're not selecting in old input field.
894     if (!m_webPage->m_inputHandler->boundingBoxForInputField().contains(relativePoint)) {
895         Node* anchorNode = selection.start().anchorNode();
896         if (!anchorNode || anchorNode->isElementNode())
897             m_webPage->m_page->focusController()->setFocusedElement(toElement(anchorNode), focusedFrame);
898     }
899
900     m_selectionActive = expandSelectionToGranularity(focusedFrame, selection, granularity, m_webPage->m_inputHandler->isInputMode());
901 }
902
903 void SelectionHandler::selectObject(TextGranularity granularity)
904 {
905     ASSERT(m_webPage && m_webPage->m_inputHandler);
906     // Using caret location, must be inside an input field.
907     if (!m_webPage->m_inputHandler->isInputMode())
908         return;
909
910     ASSERT(m_webPage->focusedOrMainFrame() && m_webPage->focusedOrMainFrame()->selection());
911     Frame* focusedFrame = m_webPage->focusedOrMainFrame();
912
913     SelectionLog(Platform::LogLevelInfo, "SelectionHandler::selectObject using current selection");
914
915     ASSERT(focusedFrame->selection()->selectionType() != VisibleSelection::NoSelection);
916
917     // Use the current selection as the selection point.
918     VisibleSelection selectionOrigin = focusedFrame->selection()->selection();
919
920     // If this is the end of the input field, make sure we select the last word.
921     if (m_webPage->m_inputHandler->isCaretAtEndOfText())
922         selectionOrigin = previousWordPosition(selectionOrigin.start());
923
924     m_selectionActive = expandSelectionToGranularity(focusedFrame, selectionOrigin, granularity, true /* isInputMode */);
925 }
926
927 void SelectionHandler::selectObject(Node* node)
928 {
929     if (!node)
930         return;
931
932     // Clear input focus if we're not selecting text there.
933     if (node != m_webPage->m_inputHandler->currentFocusElement().get())
934         m_webPage->clearFocusNode();
935
936     m_selectionActive = true;
937
938     ASSERT(m_webPage && m_webPage->focusedOrMainFrame() && m_webPage->focusedOrMainFrame()->selection());
939     Frame* focusedFrame = m_webPage->focusedOrMainFrame();
940
941     SelectionLog(Platform::LogLevelInfo, "SelectionHandler::selectNode");
942
943     VisibleSelection selection = VisibleSelection::selectionFromContentsOfNode(node);
944     drawAnimationOverlay(regionForSelectionQuads(selection), false /* isExpandingOverlayAtConstantRate */, true /* isStartOfSelection */);
945     focusedFrame->selection()->setSelection(selection);
946     m_webPage->updateSelectionScrollView(node);
947 }
948
949 static TextDirection directionOfEnclosingBlock(FrameSelection* selection)
950 {
951     Node* enclosingBlockNode = enclosingBlock(selection->selection().extent().deprecatedNode());
952     if (!enclosingBlockNode)
953         return LTR;
954
955     if (RenderObject* renderer = enclosingBlockNode->renderer())
956         return renderer->style()->direction();
957
958     return LTR;
959 }
960
961 // Returns > 0 if p1 is "closer" to referencePoint, < 0 if p2 is "closer", 0 if they are equidistant.
962 // Because text is usually arranged in horizontal rows, distance is measured along the y-axis, with x-axis used only to break ties.
963 // If rightGravity is true, the right-most x-coordinate is chosen, otherwise teh left-most coordinate is chosen.
964 static inline int comparePointsToReferencePoint(const WebCore::IntPoint& p1, const WebCore::IntPoint& p2, const WebCore::IntPoint& referencePoint, bool rightGravity)
965 {
966     int dy1 = abs(referencePoint.y() - p1.y());
967     int dy2 = abs(referencePoint.y() - p2.y());
968     if (dy1 != dy2)
969         return dy2 - dy1;
970
971     // Same y-coordinate, choose the farthest right (or left) point.
972     if (p1.x() == p2.x())
973         return 0;
974
975     if (p1.x() > p2.x())
976         return rightGravity ? 1 : -1;
977
978     return rightGravity ? -1 : 1;
979 }
980
981 // NOTE/FIXME: Due to r77286, we are getting off-by-one results in the IntRect class counterpart implementation of the
982 //             methods below. As done in r89803, r77928 and a few others, lets use local method to fix it.
983 //             We should keep our eyes very open on it, since it can affect BackingStore very badly.
984 static WebCore::IntPoint minXMinYCorner(const WebCore::IntRect& rect) { return rect.location(); } // typically topLeft
985 static WebCore::IntPoint maxXMinYCorner(const WebCore::IntRect& rect) { return WebCore::IntPoint(rect.x() + rect.width() - 1, rect.y()); } // typically topRight
986 static WebCore::IntPoint minXMaxYCorner(const WebCore::IntRect& rect) { return WebCore::IntPoint(rect.x(), rect.y() + rect.height() - 1); } // typically bottomLeft
987 static WebCore::IntPoint maxXMaxYCorner(const WebCore::IntRect& rect) { return WebCore::IntPoint(rect.x() + rect.width() - 1, rect.y() + rect.height() - 1); } // typically bottomRight
988
989 // The caret is a one-pixel wide line down either the right or left edge of a
990 // rect, depending on the text direction.
991 static inline bool caretIsOnLeft(bool isStartCaret, bool isRTL)
992 {
993     if (isStartCaret)
994         return !isRTL;
995
996     return isRTL;
997 }
998
999 static inline WebCore::IntPoint caretLocationForRect(const WebCore::IntRect& rect, bool isStartCaret, bool isRTL)
1000 {
1001     return caretIsOnLeft(isStartCaret, isRTL) ? minXMinYCorner(rect) : maxXMinYCorner(rect);
1002 }
1003
1004 static inline WebCore::IntPoint caretComparisonPointForRect(const WebCore::IntRect& rect, bool isStartCaret, bool isRTL)
1005 {
1006     if (isStartCaret)
1007         return caretIsOnLeft(isStartCaret, isRTL) ? minXMinYCorner(rect) : maxXMinYCorner(rect);
1008
1009     return caretIsOnLeft(isStartCaret, isRTL) ? minXMaxYCorner(rect) : maxXMaxYCorner(rect);
1010 }
1011
1012 static void adjustCaretRects(WebCore::IntRect& startCaret, bool isStartCaretClippedOut, WebCore::IntRect& endCaret, bool isEndCaretClippedOut,
1013     const std::vector<Platform::IntRect> rectList, const WebCore::IntPoint& startReferencePoint, const WebCore::IntPoint& endReferencePoint, bool isRTL)
1014 {
1015     // startReferencePoint is the best guess at the top left of the selection; endReferencePoint is the best guess at the bottom right.
1016     if (isStartCaretClippedOut)
1017         startCaret.setLocation(DOMSupport::InvalidPoint);
1018     else {
1019         startCaret = rectList[0];
1020         startCaret.setLocation(caretLocationForRect(startCaret, true, isRTL));
1021         // Reset width to 1 as we are strictly interested in caret location.
1022         startCaret.setWidth(1);
1023     }
1024
1025     if (isEndCaretClippedOut)
1026         endCaret.setLocation(DOMSupport::InvalidPoint);
1027     else {
1028         endCaret = rectList[0];
1029         endCaret.setLocation(caretLocationForRect(endCaret, false, isRTL));
1030         // Reset width to 1 as we are strictly interested in caret location.
1031         endCaret.setWidth(1);
1032     }
1033
1034     if (isStartCaretClippedOut && isEndCaretClippedOut)
1035         return;
1036
1037     for (unsigned i = 1; i < rectList.size(); i++) {
1038         WebCore::IntRect currentRect(rectList[i]);
1039
1040         // Compare and update the start and end carets with their respective reference points.
1041         if (!isStartCaretClippedOut && comparePointsToReferencePoint(
1042             caretComparisonPointForRect(currentRect, true, isRTL),
1043             caretComparisonPointForRect(startCaret, true, isRTL),
1044             startReferencePoint, isRTL) > 0) {
1045                 startCaret.setLocation(caretLocationForRect(currentRect, true, isRTL));
1046                 startCaret.setHeight(currentRect.height());
1047         }
1048
1049         if (!isEndCaretClippedOut && comparePointsToReferencePoint(
1050             caretComparisonPointForRect(currentRect, false, isRTL),
1051             caretComparisonPointForRect(endCaret, false, isRTL),
1052             endReferencePoint, !isRTL) > 0) {
1053                 endCaret.setLocation(caretLocationForRect(currentRect, false, isRTL));
1054                 endCaret.setHeight(currentRect.height());
1055         }
1056     }
1057 }
1058
1059 WebCore::IntPoint SelectionHandler::clipPointToVisibleContainer(const WebCore::IntPoint& point) const
1060 {
1061     ASSERT(m_webPage->m_mainFrame && m_webPage->m_mainFrame->view());
1062
1063     Frame* frame = m_webPage->focusedOrMainFrame();
1064     WebCore::IntPoint clippedPoint = DOMSupport::convertPointToFrame(m_webPage->mainFrame(), frame, point, true /* clampToTargetFrame */);
1065
1066     if (m_webPage->m_inputHandler->isInputMode()
1067         && frame->document()->focusedNode()
1068         && frame->document()->focusedNode()->renderer()) {
1069             WebCore::IntRect boundingBox(frame->document()->focusedNode()->renderer()->absoluteBoundingBoxRect());
1070             boundingBox.inflate(-1);
1071             clippedPoint = WebCore::IntPoint(clamp(boundingBox.x(), clippedPoint.x(), boundingBox.maxX()), clamp(boundingBox.y(), clippedPoint.y(), boundingBox.maxY()));
1072     }
1073
1074     return clippedPoint;
1075 }
1076
1077 static WebCore::IntPoint referencePoint(const VisiblePosition& position, const WebCore::IntRect& boundingRect, const WebCore::IntPoint& framePosition, bool isStartCaret, bool isRTL)
1078 {
1079     // If one of the carets is invalid (this happens, for instance, if the
1080     // selection ends in an empty div) fall back to using the corner of the
1081     // entire region (which is already in frame coordinates so doesn't need
1082     // adjusting).
1083     WebCore::IntRect startCaretBounds(position.absoluteCaretBounds());
1084     startCaretBounds.move(framePosition.x(), framePosition.y());
1085     if (startCaretBounds.isEmpty() || !boundingRect.contains(startCaretBounds))
1086         startCaretBounds = boundingRect;
1087
1088     return caretComparisonPointForRect(startCaretBounds, isStartCaret, isRTL);
1089 }
1090
1091 // Check all rects in the region for a point match. The region is non-banded
1092 // and non-sorted so all must be checked.
1093 static bool regionRectListContainsPoint(const IntRectRegion& region, const WebCore::IntPoint& point)
1094 {
1095     if (!region.extents().contains(point))
1096         return false;
1097
1098     std::vector<Platform::IntRect> rectList = region.rects();
1099     for (unsigned i = 0; i < rectList.size(); i++) {
1100         if (rectList[i].contains(point))
1101             return true;
1102     }
1103     return false;
1104 }
1105
1106 bool SelectionHandler::inputNodeOverridesTouch() const
1107 {
1108     if (!m_webPage->m_inputHandler->isInputMode())
1109         return false;
1110
1111     Node* focusedNode = m_webPage->focusedOrMainFrame()->document()->focusedNode();
1112     if (!focusedNode || !focusedNode->isElementNode())
1113         return false;
1114
1115     // TODO consider caching this in InputHandler so it is only calculated once per focus.
1116     DEFINE_STATIC_LOCAL(QualifiedName, selectionTouchOverrideAttr, (nullAtom, "data-blackberry-end-selection-on-touch", nullAtom));
1117     Element* element = toElement(focusedNode);
1118     return DOMSupport::elementAttributeState(element, selectionTouchOverrideAttr) == DOMSupport::On;
1119 }
1120
1121 RequestedHandlePosition SelectionHandler::requestedSelectionHandlePosition(const VisibleSelection& selection) const
1122 {
1123     Element* element = DOMSupport::selectionContainerElement(selection);
1124     return DOMSupport::elementHandlePositionAttribute(element);
1125 }
1126
1127 // Note: This is the only function in SelectionHandler in which the coordinate
1128 // system is not entirely WebKit.
1129 void SelectionHandler::selectionPositionChanged(bool forceUpdateWithoutChange)
1130 {
1131     SelectionLog(Platform::LogLevelInfo,
1132         "SelectionHandler::selectionPositionChanged forceUpdateWithoutChange = %s",
1133         forceUpdateWithoutChange ? "true" : "false");
1134
1135     // This method can get called during WebPage shutdown process.
1136     // If that is the case, just bail out since the client is not
1137     // in a safe state of trust to request anything else from it.
1138     if (!m_webPage->m_mainFrame)
1139         return;
1140
1141     if (m_webPage->m_inputHandler->isInputMode() && m_webPage->m_inputHandler->processingChange()) {
1142         if (m_webPage->m_selectionOverlay)
1143             m_webPage->m_selectionOverlay->hide();
1144         m_webPage->m_client->cancelSelectionVisuals();
1145
1146         // Since we're not calling notifyCaretPositionChangedIfNeeded now, we have to do so at the end of processing
1147         // to avoid dropping a notification.
1148         m_didSuppressCaretPositionChangedNotification = true;
1149         return;
1150     }
1151
1152     notifyCaretPositionChangedIfNeeded(m_webPage->m_touchEventHandler->m_userTriggeredTouchPressOnTextInput);
1153
1154     // Enter selection mode if selection type is RangeSelection, and disable selection if
1155     // selection is active and becomes caret selection.
1156     Frame* frame = m_webPage->focusedOrMainFrame();
1157
1158     if (frame->view()->needsLayout())
1159         return;
1160
1161     WebCore::IntPoint framePos = m_webPage->frameOffset(frame);
1162     if (m_selectionActive && (m_caretActive || frame->selection()->isNone()))
1163         m_selectionActive = false;
1164     else if (frame->selection()->isRange())
1165         m_selectionActive = true;
1166     else if (!m_selectionActive)
1167         return;
1168
1169     if (Node* focusedNode = frame->document()->focusedNode()) {
1170         if (focusedNode->hasTagName(HTMLNames::selectTag) || (focusedNode->isElementNode() && DOMSupport::isPopupInputField(toElement(focusedNode)))) {
1171             SelectionLog(Platform::LogLevelInfo, "SelectionHandler::selectionPositionChanged selection is on a popup control, skipping rendering.");
1172             return;
1173         }
1174     }
1175
1176     SelectionTimingLog(Platform::LogLevelInfo,
1177         "SelectionHandler::selectionPositionChanged starting at %f",
1178         m_timer.elapsed());
1179
1180     WebCore::IntRect startCaret(DOMSupport::InvalidPoint, WebCore::IntSize());
1181     WebCore::IntRect endCaret(DOMSupport::InvalidPoint, WebCore::IntSize());
1182
1183     // Get the text rects from the selections range.
1184     Vector<FloatQuad> quads;
1185     DOMSupport::visibleTextQuads(frame->selection()->selection(), quads);
1186
1187     IntRectRegion unclippedRegion;
1188     regionForTextQuads(quads, unclippedRegion, false /* shouldClipToVisibleContent */);
1189
1190     // If there is no change in selected text and the visual rects
1191     // have not changed then don't bother notifying anything.
1192     if (!forceUpdateWithoutChange && m_lastSelectionRegion.isEqual(unclippedRegion))
1193         return;
1194
1195     m_lastSelectionRegion = unclippedRegion;
1196     bool isRTL = directionOfEnclosingBlock(frame->selection()) == RTL;
1197
1198     IntRectRegion visibleSelectionRegion;
1199     if (!unclippedRegion.isEmpty()) {
1200         WebCore::IntRect unclippedStartCaret;
1201         WebCore::IntRect unclippedEndCaret;
1202
1203         WebCore::IntPoint startCaretReferencePoint = referencePoint(frame->selection()->selection().visibleStart(), unclippedRegion.extents(), framePos, true /* isStartCaret */, isRTL);
1204         WebCore::IntPoint endCaretReferencePoint = referencePoint(frame->selection()->selection().visibleEnd(), unclippedRegion.extents(), framePos, false /* isStartCaret */, isRTL);
1205
1206         adjustCaretRects(unclippedStartCaret, false /* unclipped */, unclippedEndCaret, false /* unclipped */, unclippedRegion.rects(), startCaretReferencePoint, endCaretReferencePoint, isRTL);
1207
1208         regionForTextQuads(quads, visibleSelectionRegion);
1209
1210 #if SHOWDEBUG_SELECTIONHANDLER // Don't rely just on SelectionLog to avoid loop.
1211         for (unsigned i = 0; i < unclippedRegion.numRects(); i++) {
1212             SelectionLog(Platform::LogLevelInfo,
1213                 "Rect list - Unmodified #%d, %s",
1214                 i,
1215                 unclippedRegion.rects()[i].toString().c_str());
1216         }
1217         for (unsigned i = 0; i < visibleSelectionRegion.numRects(); i++) {
1218             SelectionLog(Platform::LogLevelInfo,
1219                 "Rect list  - Clipped to Visible #%d, %s",
1220                 i,
1221                 visibleSelectionRegion.rects()[i].toString().c_str());
1222         }
1223 #endif
1224
1225         bool shouldCareAboutPossibleClippedOutSelection = frame != m_webPage->mainFrame() || m_webPage->m_inputHandler->isInputMode();
1226
1227         if (!visibleSelectionRegion.isEmpty() || shouldCareAboutPossibleClippedOutSelection) {
1228             // Adjust the handle markers to be at the end of the painted rect. When selecting links
1229             // and other elements that may have a larger visible area than needs to be rendered a gap
1230             // can exist between the handle and overlay region.
1231
1232             bool shouldClipStartCaret = !regionRectListContainsPoint(visibleSelectionRegion, unclippedStartCaret.location());
1233             bool shouldClipEndCaret = !regionRectListContainsPoint(visibleSelectionRegion, unclippedEndCaret.location());
1234
1235             // Find the top corner and bottom corner.
1236             adjustCaretRects(startCaret, shouldClipStartCaret, endCaret, shouldClipEndCaret, visibleSelectionRegion.rects(), startCaretReferencePoint, endCaretReferencePoint, isRTL);
1237         }
1238     }
1239
1240     if (!frame->selection()->selection().isBaseFirst()) {
1241         // End handle comes before start, invert the caret reference points.
1242         WebCore::IntRect tmpCaret(startCaret);
1243         startCaret = endCaret;
1244         endCaret = tmpCaret;
1245     }
1246
1247     SelectionLog(Platform::LogLevelInfo,
1248         "SelectionHandler::selectionPositionChanged Start Rect=%s End Rect=%s",
1249         Platform::IntRect(startCaret).toString().c_str(),
1250         Platform::IntRect(endCaret).toString().c_str());
1251
1252     if (m_webPage->m_selectionOverlay)
1253         m_webPage->m_selectionOverlay->draw(visibleSelectionRegion);
1254
1255     VisibleSelection currentSelection = frame->selection()->selection();
1256     SelectionDetails details(startCaret, endCaret, visibleSelectionRegion, inputNodeOverridesTouch(),
1257         m_lastSelection != currentSelection, requestedSelectionHandlePosition(frame->selection()->selection()), isRTL);
1258
1259     m_webPage->m_client->notifySelectionDetailsChanged(details);
1260     m_lastSelection = currentSelection;
1261     SelectionTimingLog(Platform::LogLevelInfo,
1262         "SelectionHandler::selectionPositionChanged completed at %f",
1263         m_timer.elapsed());
1264 }
1265
1266
1267 void SelectionHandler::notifyCaretPositionChangedIfNeeded(bool userTouchTriggeredOnTextField)
1268 {
1269     m_didSuppressCaretPositionChangedNotification = false;
1270
1271     if (m_caretActive || (m_webPage->m_inputHandler->isInputMode() && m_webPage->focusedOrMainFrame()->selection()->isCaret())) {
1272         // This may update the caret to no longer be active.
1273         caretPositionChanged(userTouchTriggeredOnTextField);
1274     }
1275 }
1276
1277 void SelectionHandler::caretPositionChanged(bool userTouchTriggeredOnTextField)
1278 {
1279     SelectionLog(Platform::LogLevelInfo, "SelectionHandler::caretPositionChanged");
1280
1281     WebCore::IntRect caretLocation;
1282     // If the input field is not active, we must be turning off the caret.
1283     if (!m_webPage->m_inputHandler->isInputMode() && m_caretActive) {
1284         m_caretActive = false;
1285         // Send an empty caret change to turn off the caret.
1286         m_webPage->m_client->notifyCaretChanged(caretLocation, userTouchTriggeredOnTextField);
1287         return;
1288     }
1289
1290     ASSERT(m_webPage && m_webPage->focusedOrMainFrame() && m_webPage->focusedOrMainFrame()->selection());
1291
1292     // This function should only reach this point if input mode is active.
1293     ASSERT(m_webPage->m_inputHandler->isInputMode());
1294
1295     WebCore::IntRect clippingRectForContent(clippingRectForVisibleContent());
1296     WebCore::IntPoint frameOffset(m_webPage->frameOffset(m_webPage->focusedOrMainFrame()));
1297     if (m_webPage->focusedOrMainFrame()->selection()->selectionType() == VisibleSelection::CaretSelection) {
1298         caretLocation = startCaretViewportRect(frameOffset);
1299         if (!caretLocation.isEmpty())
1300             caretLocation.intersect(clippingRectForContent); // Clip against the containing frame and node boundaries.
1301     }
1302
1303     m_caretActive = !caretLocation.isEmpty();
1304
1305     SelectionLog(Platform::LogLevelInfo,
1306         "SelectionHandler::caretPositionChanged caret Rect %s",
1307         Platform::IntRect(caretLocation).toString().c_str());
1308
1309     bool isSingleLineInput = m_caretActive && !m_webPage->m_inputHandler->isMultilineInputMode();
1310     WebCore::IntRect nodeBoundingBox = isSingleLineInput ? m_webPage->m_inputHandler->boundingBoxForInputField() : WebCore::IntRect();
1311
1312     if (!nodeBoundingBox.isEmpty()) {
1313         nodeBoundingBox.moveBy(frameOffset);
1314
1315         // Clip against the containing frame and node boundaries.
1316         nodeBoundingBox.intersect(clippingRectForContent);
1317     }
1318
1319     SelectionLog(Platform::LogLevelInfo,
1320         "SelectionHandler::caretPositionChanged: %s line input, single line bounding box %s%s",
1321         isSingleLineInput ? "single" : "multi",
1322         Platform::IntRect(nodeBoundingBox).toString().c_str(),
1323         m_webPage->m_inputHandler->elementText().isEmpty() ? ", empty text field" : "");
1324
1325     m_webPage->m_client->notifyCaretChanged(caretLocation, userTouchTriggeredOnTextField, isSingleLineInput, nodeBoundingBox, m_webPage->m_inputHandler->elementText().isEmpty());
1326 }
1327
1328 bool SelectionHandler::selectionContains(const WebCore::IntPoint& point)
1329 {
1330     ASSERT(m_webPage && m_webPage->focusedOrMainFrame() && m_webPage->focusedOrMainFrame()->selection());
1331     return m_webPage->focusedOrMainFrame()->selection()->contains(point);
1332 }
1333
1334 }
1335 }