[BlackBerry] expandSelectionToGranularity should only use VisibleSelection and not...
[WebKit-https.git] / Source / WebKit / blackberry / WebKitSupport / SelectionHandler.cpp
1 /*
2  * Copyright (C) 2010, 2011, 2012 Research In Motion Limited. All rights reserved.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17  */
18
19 #include "config.h"
20 #include "SelectionHandler.h"
21
22 #include "DOMSupport.h"
23 #include "Document.h"
24 #include "Editor.h"
25 #include "EditorClient.h"
26 #include "FatFingers.h"
27 #include "FloatQuad.h"
28 #include "Frame.h"
29 #include "FrameSelection.h"
30 #include "FrameView.h"
31 #include "HTMLAnchorElement.h"
32 #include "HTMLAreaElement.h"
33 #include "HitTestResult.h"
34 #include "InputHandler.h"
35 #include "IntRect.h"
36 #include "Page.h"
37 #include "RenderPart.h"
38 #include "TextGranularity.h"
39 #include "TouchEventHandler.h"
40 #include "WebPage.h"
41 #include "WebPageClient.h"
42 #include "WebPage_p.h"
43
44 #include "htmlediting.h"
45 #include "visible_units.h"
46
47 #include <BlackBerryPlatformKeyboardEvent.h>
48 #include <BlackBerryPlatformLog.h>
49
50 #include <sys/keycodes.h>
51
52 // Note: This generates a lot of logs when dumping rects lists. It will seriously
53 // impact performance. Do not enable this during performance tests.
54 #define SHOWDEBUG_SELECTIONHANDLER 0
55
56 using namespace BlackBerry::Platform;
57 using namespace WebCore;
58
59 #if SHOWDEBUG_SELECTIONHANDLER
60 #define DEBUG_SELECTION(severity, format, ...) logAlways(severity, format, ## __VA_ARGS__)
61 #else
62 #define DEBUG_SELECTION(severity, format, ...)
63 #endif // SHOWDEBUG_SELECTIONHANDLER
64
65 namespace BlackBerry {
66 namespace WebKit {
67
68 SelectionHandler::SelectionHandler(WebPagePrivate* page)
69     : m_webPage(page)
70     , m_selectionActive(false)
71     , m_caretActive(false)
72     , m_lastUpdatedEndPointIsValid(false)
73 {
74 }
75
76 SelectionHandler::~SelectionHandler()
77 {
78 }
79
80 void SelectionHandler::cancelSelection()
81 {
82     m_selectionActive = false;
83     m_lastSelectionRegion = IntRectRegion();
84
85     DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::cancelSelection");
86
87     if (m_webPage->m_inputHandler->isInputMode())
88         m_webPage->m_inputHandler->cancelSelection();
89     else
90         m_webPage->focusedOrMainFrame()->selection()->clear();
91 }
92
93 WebString SelectionHandler::selectedText() const
94 {
95     return m_webPage->focusedOrMainFrame()->editor()->selectedText();
96 }
97
98 WebCore::IntRect SelectionHandler::clippingRectForVisibleContent() const
99 {
100     // Get the containing content rect for the frame.
101     Frame* frame = m_webPage->focusedOrMainFrame();
102     WebCore::IntRect clipRect = WebCore::IntRect(WebCore::IntPoint(0, 0), m_webPage->contentsSize());
103     if (frame != m_webPage->mainFrame()) {
104         clipRect = m_webPage->getRecursiveVisibleWindowRect(frame->view(), true /* no clip to main frame window */);
105         clipRect = m_webPage->m_mainFrame->view()->windowToContents(clipRect);
106     }
107
108     // Get the input field containing box.
109     WebCore::IntRect inputBoundingBox = m_webPage->m_inputHandler->boundingBoxForInputField();
110     if (!inputBoundingBox.isEmpty()) {
111         // Adjust the bounding box to the frame offset.
112         inputBoundingBox = m_webPage->mainFrame()->view()->windowToContents(frame->view()->contentsToWindow(inputBoundingBox));
113         clipRect.intersect(inputBoundingBox);
114     }
115     return clipRect;
116 }
117
118 void SelectionHandler::regionForTextQuads(Vector<FloatQuad> &quadList, IntRectRegion& region, bool shouldClipToVisibleContent) const
119 {
120     ASSERT(region.isEmpty());
121
122     if (!quadList.isEmpty()) {
123         FrameView* frameView = m_webPage->focusedOrMainFrame()->view();
124
125         // frameRect is in frame coordinates.
126         WebCore::IntRect frameRect(WebCore::IntPoint(0, 0), frameView->contentsSize());
127
128         // framePosition is in main frame coordinates.
129         WebCore::IntPoint framePosition = m_webPage->frameOffset(m_webPage->focusedOrMainFrame());
130
131         // Get the visibile content rect.
132         WebCore::IntRect clippingRect = shouldClipToVisibleContent ? clippingRectForVisibleContent() : WebCore::IntRect(-1, -1, 0, 0);
133
134         // Convert the text quads into a more platform friendy
135         // IntRectRegion and adjust for subframes.
136         Platform::IntRect selectionBoundingBox;
137         std::vector<Platform::IntRect> adjustedIntRects;
138         for (unsigned i = 0; i < quadList.size(); i++) {
139             WebCore::IntRect enclosingRect = quadList[i].enclosingBoundingBox();
140             enclosingRect.intersect(frameRect);
141             enclosingRect.move(framePosition.x(), framePosition.y());
142
143             // Clip to the visible content.
144             if (clippingRect.location() != DOMSupport::InvalidPoint)
145                 enclosingRect.intersect(clippingRect);
146
147             adjustedIntRects.push_back(enclosingRect);
148             selectionBoundingBox = unionOfRects(enclosingRect, selectionBoundingBox);
149         }
150         region = IntRectRegion(selectionBoundingBox, adjustedIntRects.size(), adjustedIntRects);
151     }
152 }
153
154 static VisiblePosition visiblePositionForPointIgnoringClipping(const Frame& frame, const WebCore::IntPoint& framePoint)
155 {
156     // Frame::visiblePositionAtPoint hard-codes ignoreClipping=false in the
157     // call to hitTestResultAtPoint. This has a bug where some pages (such as
158     // metafilter) will return the wrong VisiblePosition for points that are
159     // outside the visible rect. To work around the bug, this is a copy of
160     // visiblePositionAtPoint which which passes ignoreClipping=true.
161     // See RIM Bug #4315.
162     HitTestResult result = frame.eventHandler()->hitTestResultAtPoint(framePoint, true /* allowShadowContent */, true /* ignoreClipping */);
163
164     Node* node = result.innerNode();
165     if (!node || node->document() != frame.document())
166         return VisiblePosition();
167
168     RenderObject* renderer = node->renderer();
169     if (!renderer)
170         return VisiblePosition();
171
172     VisiblePosition visiblePos = renderer->positionForPoint(result.localPoint());
173     if (visiblePos.isNull())
174         visiblePos = VisiblePosition(Position(createLegacyEditingPosition(node, 0)));
175
176     return visiblePos;
177 }
178
179 static unsigned short directionOfPointRelativeToRect(const WebCore::IntPoint& point, const WebCore::IntRect& rect, const bool useTopPadding = true, const bool useBottomPadding = true)
180 {
181     ASSERT(!rect.contains(point));
182
183     // Padding to prevent accidental trigger of up/down when intending to do horizontal movement.
184     const int verticalPadding = 5;
185
186     // Do height movement check first but add padding. We may be off on both x & y axis and only
187     // want to move in one direction at a time.
188     if (point.y() + (useTopPadding ? verticalPadding : 0) < rect.y())
189         return KEYCODE_UP;
190     if (point.y() > rect.maxY() + (useBottomPadding ? verticalPadding : 0))
191         return KEYCODE_DOWN;
192     if (point.x() < rect.location().x())
193         return KEYCODE_LEFT;
194     if (point.x() > rect.maxX())
195         return KEYCODE_RIGHT;
196
197     return 0;
198 }
199
200 bool SelectionHandler::shouldUpdateSelectionOrCaretForPoint(const WebCore::IntPoint& point, const WebCore::IntRect& caretRect, bool startCaret) const
201 {
202     ASSERT(m_webPage->m_inputHandler->isInputMode());
203
204     // If the point isn't valid don't block change as it is not actually changing.
205     if (point == DOMSupport::InvalidPoint)
206         return true;
207
208     VisibleSelection currentSelection = m_webPage->focusedOrMainFrame()->selection()->selection();
209
210     // If the input field is single line or we are on the first or last
211     // line of a multiline input field only horizontal movement is supported.
212     bool aboveCaret = point.y() < caretRect.y();
213     bool belowCaret = point.y() >= caretRect.maxY();
214
215     DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::shouldUpdateSelectionOrCaretForPoint multiline = %s above = %s below = %s first line = %s last line = %s start = %s \n"
216             , m_webPage->m_inputHandler->isMultilineInputMode() ? "true" : "false", aboveCaret ? "true" : "false", belowCaret ? "true" : "false"
217             , inSameLine(currentSelection.visibleStart(), startOfEditableContent(currentSelection.visibleStart())) ? "true" : "false"
218             , inSameLine(currentSelection.visibleEnd(), endOfEditableContent(currentSelection.visibleEnd())) ? "true" : "false"
219             , startCaret ? "true" : "false");
220
221     if (!m_webPage->m_inputHandler->isMultilineInputMode() && (aboveCaret || belowCaret))
222         return false;
223     if (startCaret && inSameLine(currentSelection.visibleStart(), startOfEditableContent(currentSelection.visibleStart())) && aboveCaret)
224         return false;
225     if (!startCaret && inSameLine(currentSelection.visibleEnd(), endOfEditableContent(currentSelection.visibleEnd())) && belowCaret)
226         return false;
227
228     return true;
229 }
230
231 void SelectionHandler::setCaretPosition(const WebCore::IntPoint &position)
232 {
233     if (!m_webPage->m_inputHandler->isInputMode())
234         return;
235
236     m_caretActive = true;
237
238     DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::setCaretPosition requested point %d, %d", position.x(), position.y());
239
240     Frame* focusedFrame = m_webPage->focusedOrMainFrame();
241     FrameSelection* controller = focusedFrame->selection();
242     WebCore::IntPoint relativePoint = DOMSupport::convertPointToFrame(m_webPage->mainFrame(), focusedFrame, position);
243     WebCore::IntRect currentCaretRect = controller->selection().visibleStart().absoluteCaretBounds();
244
245     if (relativePoint == DOMSupport::InvalidPoint || !shouldUpdateSelectionOrCaretForPoint(relativePoint, currentCaretRect)) {
246         selectionPositionChanged();
247         return;
248     }
249
250     VisiblePosition visibleCaretPosition(focusedFrame->visiblePositionForPoint(relativePoint));
251
252     if (!DOMSupport::isPositionInNode(m_webPage->focusedOrMainFrame()->document()->focusedNode(), visibleCaretPosition.deepEquivalent())) {
253         if (unsigned short character = directionOfPointRelativeToRect(relativePoint, currentCaretRect))
254             m_webPage->m_inputHandler->handleKeyboardInput(Platform::KeyboardEvent(character));
255
256         selectionPositionChanged();
257         return;
258     }
259
260     VisibleSelection newSelection(visibleCaretPosition);
261     if (controller->selection() == newSelection) {
262         selectionPositionChanged();
263         return;
264     }
265
266     controller->setSelection(newSelection);
267
268     DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::setCaretPosition point valid, cursor updated");
269 }
270
271 // This function makes sure we are not reducing the selection to a caret selection.
272 static bool shouldExtendSelectionInDirection(const VisibleSelection& selection, unsigned short character)
273 {
274     FrameSelection tempSelection;
275     tempSelection.setSelection(selection);
276     switch (character) {
277     case KEYCODE_LEFT:
278         tempSelection.modify(FrameSelection::AlterationExtend, DirectionLeft, CharacterGranularity);
279         break;
280     case KEYCODE_RIGHT:
281         tempSelection.modify(FrameSelection::AlterationExtend, DirectionRight, CharacterGranularity);
282         break;
283     case KEYCODE_UP:
284         tempSelection.modify(FrameSelection::AlterationExtend, DirectionBackward, LineGranularity);
285         break;
286     case KEYCODE_DOWN:
287         tempSelection.modify(FrameSelection::AlterationExtend, DirectionForward, LineGranularity);
288         break;
289     default:
290         break;
291     }
292
293     if ((character == KEYCODE_LEFT || character == KEYCODE_RIGHT)
294         && (!inSameLine(selection.visibleStart(), tempSelection.selection().visibleStart())
295            || !inSameLine(selection.visibleEnd(), tempSelection.selection().visibleEnd())))
296         return false;
297
298     return tempSelection.selection().selectionType() == VisibleSelection::RangeSelection;
299 }
300
301 static int clamp(const int min, const int value, const int max)
302 {
303     return value < min ? min : std::min(value, max);
304 }
305
306 static VisiblePosition directionalVisiblePositionAtExtentOfBox(Frame* frame, const WebCore::IntRect& boundingBox, unsigned short direction, const WebCore::IntPoint& basePoint)
307 {
308     ASSERT(frame);
309
310     if (!frame)
311         return VisiblePosition();
312
313     switch (direction) {
314     case KEYCODE_LEFT:
315         // Extend x to start and clamp y to the edge of bounding box.
316         return frame->visiblePositionForPoint(WebCore::IntPoint(boundingBox.x(), clamp(boundingBox.y(), basePoint.y(), boundingBox.maxY())));
317     case KEYCODE_RIGHT:
318         // Extend x to end and clamp y to the edge of bounding box.
319         return frame->visiblePositionForPoint(WebCore::IntPoint(boundingBox.maxX(), clamp(boundingBox.y(), basePoint.y(), boundingBox.maxY())));
320     case KEYCODE_UP:
321         // Extend y to top and clamp x to the edge of bounding box.
322         return frame->visiblePositionForPoint(WebCore::IntPoint(clamp(boundingBox.x(), basePoint.x(), boundingBox.maxX()), boundingBox.y()));
323     case KEYCODE_DOWN:
324         // Extend y to bottom and clamp x to the edge of bounding box.
325         return frame->visiblePositionForPoint(WebCore::IntPoint(clamp(boundingBox.x(), basePoint.x(), boundingBox.maxX()), boundingBox.maxY()));
326     default:
327         break;
328     }
329
330     return frame->visiblePositionForPoint(WebCore::IntPoint(basePoint.x(), basePoint.y()));
331 }
332
333 static bool pointIsOutsideOfBoundingBoxInDirection(unsigned direction, const WebCore::IntPoint& selectionPoint, const WebCore::IntRect& boundingBox)
334 {
335     if ((direction == KEYCODE_LEFT && selectionPoint.x() < boundingBox.x())
336         || (direction == KEYCODE_UP && selectionPoint.y() < boundingBox.y())
337         || (direction == KEYCODE_RIGHT && selectionPoint.x() > boundingBox.maxX())
338         || (direction == KEYCODE_DOWN && selectionPoint.y() > boundingBox.maxY()))
339         return true;
340
341     return false;
342 }
343
344 unsigned short SelectionHandler::extendSelectionToFieldBoundary(bool isStartHandle, const WebCore::IntPoint& selectionPoint, VisibleSelection& newSelection)
345 {
346     Frame* focusedFrame = m_webPage->focusedOrMainFrame();
347     if (!focusedFrame->document()->focusedNode() || !focusedFrame->document()->focusedNode()->renderer())
348         return 0;
349
350     FrameSelection* controller = focusedFrame->selection();
351
352     WebCore::IntRect caretRect = isStartHandle ? controller->selection().visibleStart().absoluteCaretBounds()
353                                       : controller->selection().visibleEnd().absoluteCaretBounds();
354
355     WebCore::IntRect nodeBoundingBox = focusedFrame->document()->focusedNode()->renderer()->absoluteBoundingBoxRect();
356     nodeBoundingBox.inflate(-1);
357
358     // Start handle is outside of the field. Treat it as the changed handle and move
359     // relative to the start caret rect.
360     unsigned short character = directionOfPointRelativeToRect(selectionPoint, caretRect, isStartHandle /* useTopPadding */, !isStartHandle /* useBottomPadding */);
361
362     // Prevent incorrect movement, handles can only extend the selection this way
363     // to prevent inversion of the handles.
364     if (isStartHandle && (character == KEYCODE_RIGHT || character == KEYCODE_DOWN)
365         || !isStartHandle && (character == KEYCODE_LEFT || character == KEYCODE_UP))
366         character = 0;
367
368     VisiblePosition newVisiblePosition = isStartHandle ? controller->selection().extent() : controller->selection().base();
369     // Extend the selection to the bounds of the box before doing incremental scroll if the point is outside the node.
370     // Don't extend selection and handle the character at the same time.
371     if (pointIsOutsideOfBoundingBoxInDirection(character, selectionPoint, nodeBoundingBox))
372         newVisiblePosition = directionalVisiblePositionAtExtentOfBox(focusedFrame, nodeBoundingBox, character, selectionPoint);
373
374     if (isStartHandle)
375         newSelection = VisibleSelection(newVisiblePosition, newSelection.extent(), true /* isDirectional */);
376     else
377         newSelection = VisibleSelection(newSelection.base(), newVisiblePosition, true /* isDirectional */);
378
379     // If no selection will be changed, return the character to extend using navigation.
380     if (controller->selection() == newSelection)
381         return character;
382
383     // Selection has been updated.
384     return 0;
385 }
386
387 // Returns true if handled.
388 bool SelectionHandler::updateOrHandleInputSelection(VisibleSelection& newSelection, const WebCore::IntPoint& relativeStart
389                                                     , const WebCore::IntPoint& relativeEnd)
390 {
391     ASSERT(m_webPage->m_inputHandler->isInputMode());
392
393     Frame* focusedFrame = m_webPage->focusedOrMainFrame();
394     Node* focusedNode = focusedFrame->document()->focusedNode();
395     if (!focusedNode || !focusedNode->renderer())
396         return false;
397
398     FrameSelection* controller = focusedFrame->selection();
399
400     WebCore::IntRect currentStartCaretRect = controller->selection().visibleStart().absoluteCaretBounds();
401     WebCore::IntRect currentEndCaretRect = controller->selection().visibleEnd().absoluteCaretBounds();
402
403     // Check if the handle movement is valid.
404     if (!shouldUpdateSelectionOrCaretForPoint(relativeStart, currentStartCaretRect, true /* startCaret */)
405         || !shouldUpdateSelectionOrCaretForPoint(relativeEnd, currentEndCaretRect, false /* startCaret */)) {
406         selectionPositionChanged();
407         return true;
408     }
409
410     WebCore::IntRect nodeBoundingBox = focusedNode->renderer()->absoluteBoundingBoxRect();
411
412     // Only do special handling if one handle is outside of the node.
413     bool startIsOutsideOfField = relativeStart != DOMSupport::InvalidPoint && !nodeBoundingBox.contains(relativeStart);
414     bool endIsOutsideOfField = relativeEnd != DOMSupport::InvalidPoint && !nodeBoundingBox.contains(relativeEnd);
415     if (startIsOutsideOfField && endIsOutsideOfField)
416         return false;
417
418     unsigned short character = 0;
419     if (startIsOutsideOfField) {
420         character = extendSelectionToFieldBoundary(true /* isStartHandle */, relativeStart, newSelection);
421         if (character) {
422             // Invert the selection so that the cursor point is at the beginning.
423             controller->setSelection(VisibleSelection(controller->selection().end(), controller->selection().start(), true /* isDirectional */));
424         }
425     } else if (endIsOutsideOfField) {
426         character = extendSelectionToFieldBoundary(false /* isStartHandle */, relativeEnd, newSelection);
427         if (character) {
428             // Reset the selection so that the end is the edit point.
429             controller->setSelection(VisibleSelection(controller->selection().start(), controller->selection().end(), true /* isDirectional */));
430         }
431     }
432
433     if (!character)
434         return false;
435
436     DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::setSelection making selection change attempt using key event %d", character);
437
438     if (shouldExtendSelectionInDirection(controller->selection(), character))
439         m_webPage->m_inputHandler->handleKeyboardInput(Platform::KeyboardEvent(character, Platform::KeyboardEvent::KeyDown, KEYMOD_SHIFT));
440
441     // Must send the selectionPositionChanged every time, sometimes this will duplicate but an accepted
442     // handleNavigationMove may not make an actual selection change.
443     selectionPositionChanged();
444     return true;
445 }
446
447 void SelectionHandler::setSelection(const WebCore::IntPoint& start, const WebCore::IntPoint& end)
448 {
449     m_selectionActive = true;
450
451     ASSERT(m_webPage);
452     ASSERT(m_webPage->focusedOrMainFrame());
453     ASSERT(m_webPage->focusedOrMainFrame()->selection());
454
455     Frame* focusedFrame = m_webPage->focusedOrMainFrame();
456     FrameSelection* controller = focusedFrame->selection();
457
458     DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::setSelection adjusted points %d, %d, %d, %d", start.x(), start.y(), end.x(), end.y());
459
460     // Note that IntPoint(-1, -1) is being our sentinel so far for
461     // clipped out selection starting or ending location.
462     bool startIsValid = start != DOMSupport::InvalidPoint;
463     m_lastUpdatedEndPointIsValid = end != DOMSupport::InvalidPoint;
464
465     // At least one of the locations must be valid.
466     ASSERT(startIsValid || m_lastUpdatedEndPointIsValid);
467
468     WebCore::IntPoint relativeStart = start;
469     WebCore::IntPoint relativeEnd = end;
470
471     VisibleSelection newSelection(controller->selection());
472
473     // We need the selection to be ordered base then extent.
474     if (!controller->selection().isBaseFirst())
475         controller->setSelection(VisibleSelection(controller->selection().start(), controller->selection().end(), true /* isDirectional */));
476
477     // We don't return early in the following, so that we can do input field scrolling if the
478     // handle is outside the bounds of the field. This can be extended to handle sub-region
479     // scrolling as well
480     if (startIsValid) {
481         relativeStart = DOMSupport::convertPointToFrame(m_webPage->mainFrame(), focusedFrame, start);
482
483         VisiblePosition base = visiblePositionForPointIgnoringClipping(*focusedFrame, clipPointToVisibleContainer(start));
484         if (base.isNotNull()) {
485             // The function setBase validates the "base"
486             newSelection.setBase(base);
487             newSelection.setWithoutValidation(newSelection.base(), controller->selection().end());
488             // Don't return early.
489         }
490     }
491
492     if (m_lastUpdatedEndPointIsValid) {
493         relativeEnd = DOMSupport::convertPointToFrame(m_webPage->mainFrame(), focusedFrame, end);
494
495         VisiblePosition extent = visiblePositionForPointIgnoringClipping(*focusedFrame, clipPointToVisibleContainer(end));
496         if (extent.isNotNull()) {
497             // The function setExtent validates the "extent"
498             newSelection.setExtent(extent);
499             newSelection.setWithoutValidation(controller->selection().start(), newSelection.extent());
500             // Don't return early.
501         }
502     }
503
504     newSelection.setIsDirectional(true);
505
506     if (m_webPage->m_inputHandler->isInputMode()) {
507         if (updateOrHandleInputSelection(newSelection, relativeStart, relativeEnd))
508             return;
509     }
510
511     if (controller->selection() == newSelection) {
512         selectionPositionChanged();
513         return;
514     }
515
516     // If the selection size is reduce to less than a character, selection type becomes
517     // Caret. As long as it is still a range, it's a valid selection. Selection cannot
518     // be cancelled through this function.
519     Vector<FloatQuad> quads;
520     DOMSupport::visibleTextQuads(newSelection, quads);
521
522     IntRectRegion unclippedRegion;
523     regionForTextQuads(quads, unclippedRegion, false /* shouldClipToVisibleContent */);
524     if (!unclippedRegion.isEmpty()) {
525         // Check if the handles reversed position.
526         if (m_selectionActive && !newSelection.isBaseFirst())
527             m_webPage->m_client->notifySelectionHandlesReversed();
528
529         controller->setSelection(newSelection);
530
531         DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::setSelection selection points valid, selection updated\n");
532     } else {
533         // Requested selection results in an empty selection, skip this change.
534         selectionPositionChanged();
535
536         DEBUG_SELECTION(LogLevelWarn, "SelectionHandler::setSelection selection points invalid, selection not updated\n");
537     }
538 }
539
540 // FIXME re-use this in context. Must be updated to include an option to return the href.
541 // This function should be moved to a new unit file. Names suggetions include DOMQueries
542 // and NodeTypes. Functions currently in InputHandler.cpp, SelectionHandler.cpp and WebPage.cpp
543 // can all be moved in.
544 static Node* enclosingLinkEventParentForNode(Node* node)
545 {
546     if (!node)
547         return 0;
548
549     Node* linkNode = node->enclosingLinkEventParentOrSelf();
550     return linkNode && linkNode->isLink() ? linkNode : 0;
551 }
552
553 void SelectionHandler::selectAtPoint(const WebCore::IntPoint& location)
554 {
555     // If point is invalid trigger selection based expansion.
556     if (location == DOMSupport::InvalidPoint) {
557         selectObject(WordGranularity);
558         return;
559     }
560
561     Node* targetNode;
562     WebCore::IntPoint targetPosition;
563     // FIXME: Factory this get right fat finger code into a helper.
564     const FatFingersResult lastFatFingersResult = m_webPage->m_touchEventHandler->lastFatFingersResult();
565     if (lastFatFingersResult.positionWasAdjusted() && lastFatFingersResult.nodeAsElementIfApplicable()) {
566         targetNode = lastFatFingersResult.node(FatFingersResult::ShadowContentNotAllowed);
567         targetPosition = lastFatFingersResult.adjustedPosition();
568     } else {
569         FatFingersResult newFatFingersResult = FatFingers(m_webPage, location, FatFingers::Text).findBestPoint();
570         if (!newFatFingersResult.positionWasAdjusted())
571             return;
572
573         targetPosition = newFatFingersResult.adjustedPosition();
574         targetNode = newFatFingersResult.node(FatFingersResult::ShadowContentNotAllowed);
575     }
576
577     ASSERT(targetNode);
578
579     // If the node at the point is a link, focus on the entire link, not a word.
580     if (Node* link = enclosingLinkEventParentForNode(targetNode)) {
581         selectObject(link);
582         return;
583     }
584
585     // selectAtPoint API currently only supports WordGranularity but may be extended in the future.
586     selectObject(targetPosition, WordGranularity);
587 }
588
589 static bool expandSelectionToGranularity(Frame* frame, VisibleSelection selection, TextGranularity granularity, bool isInputMode)
590 {
591     ASSERT(frame);
592     ASSERT(frame->selection());
593
594     if (!(selection.start().anchorNode() && selection.start().anchorNode()->isTextNode()))
595         return false;
596
597     if (granularity == WordGranularity)
598         selection = DOMSupport::visibleSelectionForClosestActualWordStart(selection);
599
600     selection.expandUsingGranularity(granularity);
601     selection.setAffinity(frame->selection()->affinity());
602
603     if (isInputMode && !frame->selection()->shouldChangeSelection(selection))
604         return false;
605
606     frame->selection()->setSelection(selection);
607     return true;
608 }
609
610 void SelectionHandler::selectObject(const WebCore::IntPoint& location, TextGranularity granularity)
611 {
612     ASSERT(location.x() >= 0 && location.y() >= 0);
613     ASSERT(m_webPage && m_webPage->focusedOrMainFrame() && m_webPage->focusedOrMainFrame()->selection());
614     Frame* focusedFrame = m_webPage->focusedOrMainFrame();
615
616     DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::selectObject adjusted points %d, %d", location.x(), location.y());
617
618     WebCore::IntPoint relativePoint = DOMSupport::convertPointToFrame(m_webPage->mainFrame(), focusedFrame, location);
619     VisiblePosition pointLocation(focusedFrame->visiblePositionForPoint(relativePoint));
620     VisibleSelection selection = VisibleSelection(pointLocation, pointLocation);
621
622     m_selectionActive = expandSelectionToGranularity(focusedFrame, selection, granularity, m_webPage->m_inputHandler->isInputMode());
623 }
624
625 void SelectionHandler::selectObject(TextGranularity granularity)
626 {
627     ASSERT(m_webPage && m_webPage->m_inputHandler);
628     // Using caret location, must be inside an input field.
629     if (!m_webPage->m_inputHandler->isInputMode())
630         return;
631
632     ASSERT(m_webPage->focusedOrMainFrame() && m_webPage->focusedOrMainFrame()->selection());
633     Frame* focusedFrame = m_webPage->focusedOrMainFrame();
634
635     DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::selectObject using current selection");
636
637     // Use the current selection as the selection point.
638     ASSERT(focusedFrame->selection()->selectionType() != VisibleSelection::NoSelection);
639     m_selectionActive = expandSelectionToGranularity(focusedFrame, focusedFrame->selection()->selection(), granularity, true /* isInputMode */);
640 }
641
642 void SelectionHandler::selectObject(Node* node)
643 {
644     if (!node)
645         return;
646
647     m_selectionActive = true;
648
649     ASSERT(m_webPage && m_webPage->focusedOrMainFrame() && m_webPage->focusedOrMainFrame()->selection());
650     Frame* focusedFrame = m_webPage->focusedOrMainFrame();
651
652     DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::selectNode");
653
654     VisibleSelection selection = VisibleSelection::selectionFromContentsOfNode(node);
655     focusedFrame->selection()->setSelection(selection);
656 }
657
658 static TextDirection directionOfEnclosingBlock(FrameSelection* selection)
659 {
660     Node* enclosingBlockNode = enclosingBlock(selection->selection().extent().deprecatedNode());
661     if (!enclosingBlockNode)
662         return LTR;
663
664     if (RenderObject* renderer = enclosingBlockNode->renderer())
665         return renderer->style()->direction();
666
667     return LTR;
668 }
669
670 // Returns > 0 if p1 is "closer" to referencePoint, < 0 if p2 is "closer", 0 if they are equidistant.
671 // Because text is usually arranged in horizontal rows, distance is measured along the y-axis, with x-axis used only to break ties.
672 // If rightGravity is true, the right-most x-coordinate is chosen, otherwise teh left-most coordinate is chosen.
673 static inline int comparePointsToReferencePoint(const WebCore::IntPoint& p1, const WebCore::IntPoint& p2, const WebCore::IntPoint& referencePoint, bool rightGravity)
674 {
675     int dy1 = abs(referencePoint.y() - p1.y());
676     int dy2 = abs(referencePoint.y() - p2.y());
677     if (dy1 != dy2)
678         return dy2 - dy1;
679
680     // Same y-coordinate, choose the farthest right (or left) point.
681     if (p1.x() == p2.x())
682         return 0;
683
684     if (p1.x() > p2.x())
685         return rightGravity ? 1 : -1;
686
687     return rightGravity ? -1 : 1;
688 }
689
690 // NOTE/FIXME: Due to r77286, we are getting off-by-one results in the IntRect class counterpart implementation of the
691 //             methods below. As done in r89803, r77928 and a few others, lets use local method to fix it.
692 //             We should keep our eyes very open on it, since it can affect BackingStore very badly.
693 static WebCore::IntPoint minXMinYCorner(const WebCore::IntRect& rect) { return rect.location(); } // typically topLeft
694 static WebCore::IntPoint maxXMinYCorner(const WebCore::IntRect& rect) { return WebCore::IntPoint(rect.x() + rect.width() - 1, rect.y()); } // typically topRight
695 static WebCore::IntPoint minXMaxYCorner(const WebCore::IntRect& rect) { return WebCore::IntPoint(rect.x(), rect.y() + rect.height() - 1); } // typically bottomLeft
696 static WebCore::IntPoint maxXMaxYCorner(const WebCore::IntRect& rect) { return WebCore::IntPoint(rect.x() + rect.width() - 1, rect.y() + rect.height() - 1); } // typically bottomRight
697
698 // The caret is a one-pixel wide line down either the right or left edge of a
699 // rect, depending on the text direction.
700 static inline bool caretIsOnLeft(bool isStartCaret, bool isRTL)
701 {
702     if (isStartCaret)
703         return !isRTL;
704
705     return isRTL;
706 }
707
708 static inline WebCore::IntPoint caretLocationForRect(const WebCore::IntRect& rect, bool isStartCaret, bool isRTL)
709 {
710     return caretIsOnLeft(isStartCaret, isRTL) ? minXMinYCorner(rect) : maxXMinYCorner(rect);
711 }
712
713 static inline WebCore::IntPoint caretComparisonPointForRect(const WebCore::IntRect& rect, bool isStartCaret, bool isRTL)
714 {
715     if (isStartCaret)
716         return caretIsOnLeft(isStartCaret, isRTL) ? minXMinYCorner(rect) : maxXMinYCorner(rect);
717
718     return caretIsOnLeft(isStartCaret, isRTL) ? minXMaxYCorner(rect) : maxXMaxYCorner(rect);
719 }
720
721 static void adjustCaretRects(WebCore::IntRect& startCaret, bool isStartCaretClippedOut,
722                              WebCore::IntRect& endCaret, bool isEndCaretClippedOut,
723                              const std::vector<Platform::IntRect> rectList,
724                              const WebCore::IntPoint& startReferencePoint,
725                              const WebCore::IntPoint& endReferencePoint,
726                              bool isRTL)
727 {
728     // startReferencePoint is the best guess at the top left of the selection; endReferencePoint is the best guess at the bottom right.
729     if (isStartCaretClippedOut)
730         startCaret.setLocation(DOMSupport::InvalidPoint);
731     else {
732         startCaret = rectList[0];
733         startCaret.setLocation(caretLocationForRect(startCaret, true, isRTL));
734     }
735
736     if (isEndCaretClippedOut)
737         endCaret.setLocation(DOMSupport::InvalidPoint);
738     else {
739         endCaret = rectList[0];
740         endCaret.setLocation(caretLocationForRect(endCaret, false, isRTL));
741     }
742
743     if (isStartCaretClippedOut && isEndCaretClippedOut)
744         return;
745
746     // Reset width to 1 as we are strictly interested in caret location.
747     startCaret.setWidth(1);
748     endCaret.setWidth(1);
749
750     for (unsigned i = 1; i < rectList.size(); i++) {
751         WebCore::IntRect currentRect(rectList[i]);
752
753         // Compare and update the start and end carets with their respective reference points.
754         if (!isStartCaretClippedOut && comparePointsToReferencePoint(
755                     caretComparisonPointForRect(currentRect, true, isRTL),
756                     caretComparisonPointForRect(startCaret, true, isRTL),
757                     startReferencePoint, isRTL) > 0) {
758             startCaret.setLocation(caretLocationForRect(currentRect, true, isRTL));
759             startCaret.setHeight(currentRect.height());
760         }
761
762         if (!isEndCaretClippedOut && comparePointsToReferencePoint(
763                     caretComparisonPointForRect(currentRect, false, isRTL),
764                     caretComparisonPointForRect(endCaret, false, isRTL),
765                     endReferencePoint, !isRTL) > 0) {
766             endCaret.setLocation(caretLocationForRect(currentRect, false, isRTL));
767             endCaret.setHeight(currentRect.height());
768         }
769     }
770 }
771
772 WebCore::IntPoint SelectionHandler::clipPointToVisibleContainer(const WebCore::IntPoint& point) const
773 {
774     ASSERT(m_webPage->m_mainFrame && m_webPage->m_mainFrame->view());
775
776     Frame* frame = m_webPage->focusedOrMainFrame();
777     WebCore::IntPoint clippedPoint = DOMSupport::convertPointToFrame(m_webPage->mainFrame(), frame, point, true /* clampToTargetFrame */);
778
779     if (m_webPage->m_inputHandler->isInputMode()
780             && frame->document()->focusedNode()
781             && frame->document()->focusedNode()->renderer()) {
782         WebCore::IntRect boundingBox(frame->document()->focusedNode()->renderer()->absoluteBoundingBoxRect());
783         boundingBox.inflate(-1);
784         clippedPoint = WebCore::IntPoint(clamp(boundingBox.x(), clippedPoint.x(), boundingBox.maxX()), clamp(boundingBox.y(), clippedPoint.y(), boundingBox.maxY()));
785     }
786
787     return clippedPoint;
788 }
789
790 static WebCore::IntPoint referencePoint(const VisiblePosition& position, const WebCore::IntRect& boundingRect, const WebCore::IntPoint& framePosition, bool isStartCaret, bool isRTL)
791 {
792     // If one of the carets is invalid (this happens, for instance, if the
793     // selection ends in an empty div) fall back to using the corner of the
794     // entire region (which is already in frame coordinates so doesn't need
795     // adjusting).
796     WebCore::IntRect startCaretBounds(position.absoluteCaretBounds());
797     if (startCaretBounds.isEmpty())
798         startCaretBounds = boundingRect;
799     else
800         startCaretBounds.move(framePosition.x(), framePosition.y());
801
802     return caretComparisonPointForRect(startCaretBounds, isStartCaret, isRTL);
803 }
804
805 // Check all rects in the region for a point match. The region is non-banded
806 // and non-sorted so all must be checked.
807 static bool regionRectListContainsPoint(const IntRectRegion& region, const WebCore::IntPoint& point)
808 {
809     if (!region.extents().contains(point))
810         return false;
811
812     std::vector<Platform::IntRect> rectList = region.rects();
813     for (unsigned int i = 0; i < rectList.size(); i++) {
814         if (rectList[i].contains(point))
815             return true;
816     }
817     return false;
818 }
819
820 bool SelectionHandler::inputNodeOverridesTouch() const
821 {
822     if (!m_webPage->m_inputHandler->isInputMode())
823         return false;
824
825     Node* focusedNode = m_webPage->focusedOrMainFrame()->document()->focusedNode();
826     if (!focusedNode || !focusedNode->isElementNode())
827         return false;
828
829     // TODO consider caching this in InputHandler so it is only calculated once per focus.
830     DEFINE_STATIC_LOCAL(QualifiedName, selectionTouchOverrideAttr, (nullAtom, "data-blackberry-end-selection-on-touch", nullAtom));
831     Element* element = static_cast<Element*>(focusedNode);
832     return DOMSupport::elementAttributeState(element, selectionTouchOverrideAttr) == DOMSupport::On;
833 }
834
835 // Note: This is the only function in SelectionHandler in which the coordinate
836 // system is not entirely WebKit.
837 void SelectionHandler::selectionPositionChanged(bool visualChangeOnly)
838 {
839     DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::selectionPositionChanged visibleChangeOnly = %s", visualChangeOnly ? "true" : "false");
840
841     // This method can get called during WebPage shutdown process.
842     // If that is the case, just bail out since the client is not
843     // in a safe state of trust to request anything else from it.
844     if (!m_webPage->m_mainFrame)
845         return;
846
847     if (m_webPage->m_inputHandler->isInputMode() && m_webPage->m_inputHandler->processingChange()) {
848         m_webPage->m_client->cancelSelectionVisuals();
849         return;
850     }
851
852     if (m_caretActive || (m_webPage->m_inputHandler->isInputMode() && m_webPage->focusedOrMainFrame()->selection()->isCaret())) {
853         // This may update the caret to no longer be active.
854         caretPositionChanged();
855     }
856
857     // Enter selection mode if selection type is RangeSelection, and disable selection if
858     // selection is active and becomes caret selection.
859     Frame* frame = m_webPage->focusedOrMainFrame();
860     WebCore::IntPoint framePos = m_webPage->frameOffset(frame);
861     if (m_selectionActive && (m_caretActive || frame->selection()->isNone()))
862         m_selectionActive = false;
863     else if (frame->selection()->isRange())
864         m_selectionActive = true;
865     else if (!m_selectionActive)
866         return;
867
868     WebCore::IntRect startCaret;
869     WebCore::IntRect endCaret;
870
871     // Get the text rects from the selections range.
872     Vector<FloatQuad> quads;
873     DOMSupport::visibleTextQuads(frame->selection()->selection(), quads);
874
875     IntRectRegion unclippedRegion;
876     regionForTextQuads(quads, unclippedRegion, false /* shouldClipToVisibleContent */);
877
878     // If there is no change in selected text and the visual rects
879     // have not changed then don't bother notifying anything.
880     if (visualChangeOnly && m_lastSelectionRegion.isEqual(unclippedRegion))
881         return;
882
883     m_lastSelectionRegion = unclippedRegion;
884
885     IntRectRegion visibleSelectionRegion;
886     if (!unclippedRegion.isEmpty()) {
887         WebCore::IntRect unclippedStartCaret;
888         WebCore::IntRect unclippedEndCaret;
889
890         bool isRTL = directionOfEnclosingBlock(frame->selection()) == RTL;
891
892         WebCore::IntPoint startCaretReferencePoint = referencePoint(frame->selection()->selection().visibleStart(), unclippedRegion.extents(), framePos, true /* isStartCaret */, isRTL);
893         WebCore::IntPoint endCaretReferencePoint = referencePoint(frame->selection()->selection().visibleEnd(), unclippedRegion.extents(), framePos, false /* isStartCaret */, isRTL);
894
895         adjustCaretRects(unclippedStartCaret, false /* unclipped */, unclippedEndCaret, false /* unclipped */, unclippedRegion.rects(), startCaretReferencePoint, endCaretReferencePoint, isRTL);
896
897         regionForTextQuads(quads, visibleSelectionRegion);
898
899 #if SHOWDEBUG_SELECTIONHANDLER // Don't rely just on DEBUG_SELECTION to avoid loop.
900         for (unsigned int i = 0; i < unclippedRegion.numRects(); i++)
901             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());
902         for (unsigned int i = 0; i < visibleSelectionRegion.numRects(); i++)
903             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());
904 #endif
905
906         bool shouldCareAboutPossibleClippedOutSelection = frame != m_webPage->mainFrame() || m_webPage->m_inputHandler->isInputMode();
907
908         if (!visibleSelectionRegion.isEmpty() || shouldCareAboutPossibleClippedOutSelection) {
909             // Adjust the handle markers to be at the end of the painted rect. When selecting links
910             // and other elements that may have a larger visible area than needs to be rendered a gap
911             // can exist between the handle and overlay region.
912
913             bool shouldClipStartCaret = !regionRectListContainsPoint(visibleSelectionRegion, unclippedStartCaret.location());
914             bool shouldClipEndCaret = !regionRectListContainsPoint(visibleSelectionRegion, unclippedEndCaret.location());
915
916             // Find the top corner and bottom corner.
917             adjustCaretRects(startCaret, shouldClipStartCaret, endCaret, shouldClipEndCaret, visibleSelectionRegion.rects(), startCaretReferencePoint, endCaretReferencePoint, isRTL);
918
919             // Translate the caret values as they must be in transformed coordinates.
920             if (!shouldClipStartCaret) {
921                 startCaret = m_webPage->mapToTransformed(startCaret);
922                 m_webPage->clipToTransformedContentsRect(startCaret);
923             }
924
925             if (!shouldClipEndCaret) {
926                 endCaret = m_webPage->mapToTransformed(endCaret);
927                 m_webPage->clipToTransformedContentsRect(endCaret);
928             }
929         }
930     }
931
932     DEBUG_SELECTION(BlackBerry::Platform::LogLevelInfo, "SelectionHandler::selectionPositionChanged Start Rect=(%d, %d) (%d x %d) End Rect=(%d, %d) (%d x %d)",
933                     startCaret.x(), startCaret.y(), startCaret.width(), startCaret.height(), endCaret.x(), endCaret.y(), endCaret.width(), endCaret.height());
934
935
936     m_webPage->m_client->notifySelectionDetailsChanged(startCaret, endCaret, visibleSelectionRegion, inputNodeOverridesTouch());
937 }
938
939 // NOTE: This function is not in WebKit coordinates.
940 void SelectionHandler::caretPositionChanged()
941 {
942     DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::caretPositionChanged");
943
944     WebCore::IntRect caretLocation;
945     // If the input field is empty, we always turn off the caret.
946     // If the input field is not active, we must be turning off the caret.
947     bool emptyInputField = m_webPage->m_inputHandler->elementText().isEmpty();
948     if (emptyInputField || (!m_webPage->m_inputHandler->isInputMode() && m_caretActive)) {
949         if (!emptyInputField)
950             m_caretActive = false;
951         // Send an empty caret change to turn off the caret.
952         m_webPage->m_client->notifyCaretChanged(caretLocation, m_webPage->m_touchEventHandler->lastFatFingersResult().isTextInput() /* userTouchTriggered */);
953         return;
954     }
955
956     ASSERT(m_webPage && m_webPage->focusedOrMainFrame() && m_webPage->focusedOrMainFrame()->selection());
957
958     // This function should only reach this point if input mode is active.
959     ASSERT(m_webPage->m_inputHandler->isInputMode());
960
961     WebCore::IntPoint frameOffset(m_webPage->frameOffset(m_webPage->focusedOrMainFrame()));
962     WebCore::IntRect clippingRectForContent(clippingRectForVisibleContent());
963     if (m_webPage->focusedOrMainFrame()->selection()->selectionType() == VisibleSelection::CaretSelection) {
964         caretLocation = m_webPage->focusedOrMainFrame()->selection()->selection().visibleStart().absoluteCaretBounds();
965         caretLocation.move(frameOffset.x(), frameOffset.y());
966
967         // Clip against the containing frame and node boundaries.
968         caretLocation.intersect(clippingRectForContent);
969     }
970
971     m_caretActive = !caretLocation.isEmpty();
972
973     DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::caretPositionChanged caret Rect %d, %d, %dx%d",
974                         caretLocation.x(), caretLocation.y(), caretLocation.width(), caretLocation.height());
975
976     caretLocation = m_webPage->mapToTransformed(caretLocation);
977     m_webPage->clipToTransformedContentsRect(caretLocation);
978
979     bool singleLineInput = !m_webPage->m_inputHandler->isMultilineInputMode();
980     WebCore::IntRect nodeBoundingBox = singleLineInput ? m_webPage->m_inputHandler->boundingBoxForInputField() : WebCore::IntRect();
981
982     if (!nodeBoundingBox.isEmpty()) {
983         nodeBoundingBox.move(frameOffset.x(), frameOffset.y());
984
985         // Clip against the containing frame and node boundaries.
986         nodeBoundingBox.intersect(clippingRectForContent);
987
988         nodeBoundingBox = m_webPage->mapToTransformed(nodeBoundingBox);
989         m_webPage->clipToTransformedContentsRect(nodeBoundingBox);
990     }
991
992     DEBUG_SELECTION(LogLevelInfo, "SelectionHandler::single line %s single line bounding box %d, %d, %dx%d",
993                     singleLineInput ? "true" : "false", nodeBoundingBox.x(), nodeBoundingBox.y(), nodeBoundingBox.width(), nodeBoundingBox.height());
994
995     m_webPage->m_client->notifyCaretChanged(caretLocation, m_webPage->m_touchEventHandler->lastFatFingersResult().isTextInput() /* userTouchTriggered */, singleLineInput, nodeBoundingBox);
996 }
997
998 bool SelectionHandler::selectionContains(const WebCore::IntPoint& point)
999 {
1000     ASSERT(m_webPage && m_webPage->focusedOrMainFrame() && m_webPage->focusedOrMainFrame()->selection());
1001     return m_webPage->focusedOrMainFrame()->selection()->contains(point);
1002 }
1003
1004 }
1005 }