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