SelectionHandler.cpp 58 KB

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