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