| OLD | NEW |
| (Empty) | |
| 1 /* |
| 2 * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserv
ed. |
| 3 * Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org) |
| 4 * Copyright (C) 2012 Digia Plc. and/or its subsidiary(-ies) |
| 5 * |
| 6 * Redistribution and use in source and binary forms, with or without |
| 7 * modification, are permitted provided that the following conditions |
| 8 * are met: |
| 9 * 1. Redistributions of source code must retain the above copyright |
| 10 * notice, this list of conditions and the following disclaimer. |
| 11 * 2. Redistributions in binary form must reproduce the above copyright |
| 12 * notice, this list of conditions and the following disclaimer in the |
| 13 * documentation and/or other materials provided with the distribution. |
| 14 * |
| 15 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY |
| 16 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR |
| 19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| 20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| 22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| 23 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 26 */ |
| 27 |
| 28 #include "config.h" |
| 29 #include "core/editing/SelectionController.h" |
| 30 |
| 31 #include "core/HTMLNames.h" |
| 32 #include "core/dom/Document.h" |
| 33 #include "core/dom/DocumentMarkerController.h" |
| 34 #include "core/editing/Editor.h" |
| 35 #include "core/editing/FrameSelection.h" |
| 36 #include "core/editing/htmlediting.h" |
| 37 #include "core/editing/iterators/TextIterator.h" |
| 38 #include "core/events/Event.h" |
| 39 #include "core/frame/FrameView.h" |
| 40 #include "core/frame/LocalFrame.h" |
| 41 #include "core/frame/Settings.h" |
| 42 #include "core/layout/LayoutView.h" |
| 43 #include "core/page/FocusController.h" |
| 44 #include "core/page/Page.h" |
| 45 #include "platform/RuntimeEnabledFeatures.h" |
| 46 |
| 47 namespace blink { |
| 48 |
| 49 namespace { |
| 50 |
| 51 void setSelectionIfNeeded(FrameSelection& selection, const VisibleSelection& new
Selection) |
| 52 { |
| 53 if (selection.selection() == newSelection) |
| 54 return; |
| 55 selection.setSelection(newSelection); |
| 56 } |
| 57 |
| 58 bool dispatchSelectStart(Node* node) |
| 59 { |
| 60 if (!node || !node->layoutObject()) |
| 61 return true; |
| 62 |
| 63 return node->dispatchEvent(Event::createCancelableBubble(EventTypeNames::sel
ectstart)); |
| 64 } |
| 65 |
| 66 VisibleSelection expandSelectionToRespectUserSelectAll(Node* targetNode, const V
isibleSelection& selection) |
| 67 { |
| 68 Node* rootUserSelectAll = Position::rootUserSelectAllForNode(targetNode); |
| 69 if (!rootUserSelectAll) |
| 70 return selection; |
| 71 |
| 72 VisibleSelection newSelection(selection); |
| 73 newSelection.setBase(positionBeforeNode(rootUserSelectAll).upstream(CanCross
EditingBoundary)); |
| 74 newSelection.setExtent(positionAfterNode(rootUserSelectAll).downstream(CanCr
ossEditingBoundary)); |
| 75 |
| 76 return newSelection; |
| 77 } |
| 78 |
| 79 bool canMouseDownStartSelect(Node* node) |
| 80 { |
| 81 if (!node || !node->layoutObject()) |
| 82 return true; |
| 83 |
| 84 if (!node->canStartSelection()) |
| 85 return false; |
| 86 |
| 87 return true; |
| 88 } |
| 89 |
| 90 int textDistance(const Position& start, const Position& end) |
| 91 { |
| 92 RefPtrWillBeRawPtr<Range> range = Range::create(*start.document(), start, en
d); |
| 93 return TextIterator::rangeLength(range->startPosition(), range->endPosition(
), true); |
| 94 } |
| 95 |
| 96 } // anonymous namespace |
| 97 |
| 98 PassOwnPtrWillBeRawPtr<SelectionController> SelectionController::create(LocalFra
me* frame) |
| 99 { |
| 100 return adoptPtr(new SelectionController(frame)); |
| 101 } |
| 102 |
| 103 SelectionController::SelectionController(LocalFrame* frame) |
| 104 : m_frame(frame) |
| 105 , m_mouseDownMayStartSelect(false) |
| 106 , m_singleClickInSelection(false) |
| 107 , m_selectionState(SelectionState::HaveNotStartedSelection) |
| 108 { |
| 109 } |
| 110 |
| 111 DEFINE_TRACE(SelectionController) |
| 112 { |
| 113 visitor->trace(m_frame); |
| 114 } |
| 115 |
| 116 |
| 117 SelectionController::~SelectionController() |
| 118 { |
| 119 } |
| 120 |
| 121 bool SelectionController::updateSelectionForMouseDownDispatchingSelectStart(Node
* targetNode, const VisibleSelection& visibleSelection, TextGranularity granular
ity) |
| 122 { |
| 123 if (Position::nodeIsUserSelectNone(targetNode)) |
| 124 return false; |
| 125 |
| 126 if (!dispatchSelectStart(targetNode)) |
| 127 return false; |
| 128 |
| 129 if (visibleSelection.isRange()) { |
| 130 m_selectionState = SelectionState::ExtendedSelection; |
| 131 } else { |
| 132 granularity = CharacterGranularity; |
| 133 m_selectionState = SelectionState::PlacedCaret; |
| 134 } |
| 135 |
| 136 selection().setNonDirectionalSelectionIfNeeded(visibleSelection, granularity
); |
| 137 |
| 138 return true; |
| 139 } |
| 140 |
| 141 void SelectionController::selectClosestWordFromHitTestResult(const HitTestResult
& result, AppendTrailingWhitespace appendTrailingWhitespace) |
| 142 { |
| 143 Node* innerNode = result.innerNode(); |
| 144 VisibleSelection newSelection; |
| 145 |
| 146 if (innerNode && innerNode->layoutObject()) { |
| 147 VisiblePosition pos(innerNode->layoutObject()->positionForPoint(result.l
ocalPoint())); |
| 148 if (pos.isNotNull()) { |
| 149 newSelection = VisibleSelection(pos); |
| 150 newSelection.expandUsingGranularity(WordGranularity); |
| 151 } |
| 152 |
| 153 if (appendTrailingWhitespace == AppendTrailingWhitespace::ShouldAppend &
& newSelection.isRange()) |
| 154 newSelection.appendTrailingWhitespace(); |
| 155 |
| 156 updateSelectionForMouseDownDispatchingSelectStart(innerNode, expandSelec
tionToRespectUserSelectAll(innerNode, newSelection), WordGranularity); |
| 157 } |
| 158 } |
| 159 |
| 160 void SelectionController::selectClosestMisspellingFromHitTestResult(const HitTes
tResult& result, AppendTrailingWhitespace appendTrailingWhitespace) |
| 161 { |
| 162 Node* innerNode = result.innerNode(); |
| 163 VisibleSelection newSelection; |
| 164 |
| 165 if (!innerNode || !innerNode->layoutObject()) |
| 166 return; |
| 167 |
| 168 VisiblePosition pos(innerNode->layoutObject()->positionForPoint(result.local
Point())); |
| 169 Position start = pos.deepEquivalent(); |
| 170 Position end = pos.deepEquivalent(); |
| 171 if (pos.isNotNull()) { |
| 172 DocumentMarkerVector markers = innerNode->document().markers().markersIn
Range(makeRange(pos, pos).get(), DocumentMarker::MisspellingMarkers()); |
| 173 if (markers.size() == 1) { |
| 174 start.moveToOffset(markers[0]->startOffset()); |
| 175 end.moveToOffset(markers[0]->endOffset()); |
| 176 newSelection = VisibleSelection(start, end); |
| 177 } |
| 178 } |
| 179 |
| 180 if (appendTrailingWhitespace == AppendTrailingWhitespace::ShouldAppend && ne
wSelection.isRange()) |
| 181 newSelection.appendTrailingWhitespace(); |
| 182 |
| 183 updateSelectionForMouseDownDispatchingSelectStart(innerNode, expandSelection
ToRespectUserSelectAll(innerNode, newSelection), WordGranularity); |
| 184 } |
| 185 |
| 186 void SelectionController::selectClosestWordFromMouseEvent(const MouseEventWithHi
tTestResults& result) |
| 187 { |
| 188 if (!m_mouseDownMayStartSelect) |
| 189 return; |
| 190 |
| 191 selectClosestWordFromHitTestResult(result.hitTestResult(), |
| 192 (result.event().clickCount() == 2 && m_frame->editor().isSelectTrailingW
hitespaceEnabled()) ? AppendTrailingWhitespace::ShouldAppend : AppendTrailingWhi
tespace::DontAppend); |
| 193 } |
| 194 |
| 195 void SelectionController::selectClosestMisspellingFromMouseEvent(const MouseEven
tWithHitTestResults& result) |
| 196 { |
| 197 if (!m_mouseDownMayStartSelect) |
| 198 return; |
| 199 |
| 200 selectClosestMisspellingFromHitTestResult(result.hitTestResult(), |
| 201 (result.event().clickCount() == 2 && m_frame->editor().isSelectTrailingW
hitespaceEnabled()) ? AppendTrailingWhitespace::ShouldAppend : AppendTrailingWhi
tespace::DontAppend); |
| 202 } |
| 203 |
| 204 void SelectionController::selectClosestWordOrLinkFromMouseEvent(const MouseEvent
WithHitTestResults& result) |
| 205 { |
| 206 if (!result.hitTestResult().isLiveLink()) |
| 207 return selectClosestWordFromMouseEvent(result); |
| 208 |
| 209 Node* innerNode = result.innerNode(); |
| 210 |
| 211 if (!innerNode || !innerNode->layoutObject() || !m_mouseDownMayStartSelect) |
| 212 return; |
| 213 |
| 214 VisibleSelection newSelection; |
| 215 Element* URLElement = result.hitTestResult().URLElement(); |
| 216 VisiblePosition pos(innerNode->layoutObject()->positionForPoint(result.local
Point())); |
| 217 if (pos.isNotNull() && pos.deepEquivalent().deprecatedNode()->isDescendantOf
(URLElement)) |
| 218 newSelection = VisibleSelection::selectionFromContentsOfNode(URLElement)
; |
| 219 |
| 220 updateSelectionForMouseDownDispatchingSelectStart(innerNode, expandSelection
ToRespectUserSelectAll(innerNode, newSelection), WordGranularity); |
| 221 } |
| 222 |
| 223 void SelectionController::handleMousePressEvent(const MouseEventWithHitTestResul
ts& event) |
| 224 { |
| 225 // If we got the event back, that must mean it wasn't prevented, |
| 226 // so it's allowed to start a drag or selection if it wasn't in a scrollbar. |
| 227 m_mouseDownMayStartSelect = canMouseDownStartSelect(event.innerNode()) && !e
vent.scrollbar(); |
| 228 m_singleClickInSelection = false; |
| 229 } |
| 230 |
| 231 bool SelectionController::handleMousePressEventDoubleClick(const MouseEventWithH
itTestResults& event) |
| 232 { |
| 233 TRACE_EVENT0("blink", "SelectionController::handleMousePressEventDoubleClick
"); |
| 234 |
| 235 if (event.event().button() != LeftButton) |
| 236 return false; |
| 237 |
| 238 if (selection().isRange()) { |
| 239 // A double-click when range is already selected |
| 240 // should not change the selection. So, do not call |
| 241 // selectClosestWordFromMouseEvent, but do set |
| 242 // m_beganSelectingText to prevent handleMouseReleaseEvent |
| 243 // from setting caret selection. |
| 244 m_selectionState = SelectionState::ExtendedSelection; |
| 245 } else { |
| 246 selectClosestWordFromMouseEvent(event); |
| 247 } |
| 248 return true; |
| 249 } |
| 250 |
| 251 bool SelectionController::handleMousePressEventTripleClick(const MouseEventWithH
itTestResults& event) |
| 252 { |
| 253 TRACE_EVENT0("blink", "SelectionController::handleMousePressEventTripleClick
"); |
| 254 |
| 255 if (event.event().button() != LeftButton) |
| 256 return false; |
| 257 |
| 258 Node* innerNode = event.innerNode(); |
| 259 if (!(innerNode && innerNode->layoutObject() && m_mouseDownMayStartSelect)) |
| 260 return false; |
| 261 |
| 262 VisibleSelection newSelection; |
| 263 VisiblePosition pos(innerNode->layoutObject()->positionForPoint(event.localP
oint())); |
| 264 if (pos.isNotNull()) { |
| 265 newSelection = VisibleSelection(pos); |
| 266 newSelection.expandUsingGranularity(ParagraphGranularity); |
| 267 } |
| 268 |
| 269 return updateSelectionForMouseDownDispatchingSelectStart(innerNode, expandSe
lectionToRespectUserSelectAll(innerNode, newSelection), ParagraphGranularity); |
| 270 } |
| 271 |
| 272 bool SelectionController::handleMousePressEventSingleClick(const MouseEventWithH
itTestResults& event) |
| 273 { |
| 274 TRACE_EVENT0("blink", "SelectionController::handleMousePressEventSingleClick
"); |
| 275 |
| 276 m_frame->document()->updateLayoutIgnorePendingStylesheets(); |
| 277 Node* innerNode = event.innerNode(); |
| 278 if (!(innerNode && innerNode->layoutObject() && m_mouseDownMayStartSelect)) |
| 279 return false; |
| 280 |
| 281 // Extend the selection if the Shift key is down, unless the click is in a l
ink. |
| 282 bool extendSelection = event.event().shiftKey() && !event.isOverLink(); |
| 283 |
| 284 // Don't restart the selection when the mouse is pressed on an |
| 285 // existing selection so we can allow for text dragging. |
| 286 if (FrameView* view = m_frame->view()) { |
| 287 LayoutPoint vPoint = view->rootFrameToContents(event.event().position())
; |
| 288 if (!extendSelection && selection().contains(vPoint)) { |
| 289 m_singleClickInSelection = true; |
| 290 return false; |
| 291 } |
| 292 } |
| 293 |
| 294 VisiblePosition visiblePos(innerNode->layoutObject()->positionForPoint(event
.localPoint())); |
| 295 if (visiblePos.isNull()) |
| 296 visiblePos = VisiblePosition(firstPositionInOrBeforeNode(innerNode), DOW
NSTREAM); |
| 297 Position pos = visiblePos.deepEquivalent(); |
| 298 |
| 299 VisibleSelection newSelection = selection().selection(); |
| 300 TextGranularity granularity = CharacterGranularity; |
| 301 |
| 302 if (extendSelection && newSelection.isCaretOrRange()) { |
| 303 VisibleSelection selectionInUserSelectAll(expandSelectionToRespectUserSe
lectAll(innerNode, VisibleSelection(VisiblePosition(pos)))); |
| 304 if (selectionInUserSelectAll.isRange()) { |
| 305 if (comparePositions(selectionInUserSelectAll.start(), newSelection.
start()) < 0) |
| 306 pos = selectionInUserSelectAll.start(); |
| 307 else if (comparePositions(newSelection.end(), selectionInUserSelectA
ll.end()) < 0) |
| 308 pos = selectionInUserSelectAll.end(); |
| 309 } |
| 310 |
| 311 if (!m_frame->editor().behavior().shouldConsiderSelectionAsDirectional()
) { |
| 312 if (pos.isNotNull()) { |
| 313 // See <rdar://problem/3668157> REGRESSION (Mail): shift-click d
eselects when selection |
| 314 // was created right-to-left |
| 315 Position start = newSelection.start(); |
| 316 Position end = newSelection.end(); |
| 317 int distanceToStart = textDistance(start, pos); |
| 318 int distanceToEnd = textDistance(pos, end); |
| 319 if (distanceToStart <= distanceToEnd) |
| 320 newSelection = VisibleSelection(end, pos); |
| 321 else |
| 322 newSelection = VisibleSelection(start, pos); |
| 323 } |
| 324 } else { |
| 325 newSelection.setExtent(pos); |
| 326 } |
| 327 |
| 328 if (selection().granularity() != CharacterGranularity) { |
| 329 granularity = selection().granularity(); |
| 330 newSelection.expandUsingGranularity(selection().granularity()); |
| 331 } |
| 332 } else if (m_selectionState != SelectionState::ExtendedSelection) { |
| 333 newSelection = expandSelectionToRespectUserSelectAll(innerNode, VisibleS
election(visiblePos)); |
| 334 } |
| 335 |
| 336 // Updating the selection is considered side-effect of the event and so it d
oesn't impact the handled state. |
| 337 updateSelectionForMouseDownDispatchingSelectStart(innerNode, newSelection, g
ranularity); |
| 338 return false; |
| 339 } |
| 340 |
| 341 void SelectionController::handleMouseDraggedEvent(const MouseEventWithHitTestRes
ults& event, const IntPoint& mouseDownPos, const LayoutPoint& dragStartPos, Node
* mousePressNode, const IntPoint& lastKnownMousePosition) |
| 342 { |
| 343 if (m_selectionState != SelectionState::ExtendedSelection) { |
| 344 HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active
); |
| 345 HitTestResult result(request, mouseDownPos); |
| 346 m_frame->document()->layoutView()->hitTest(result); |
| 347 |
| 348 updateSelectionForMouseDrag(result, mousePressNode, dragStartPos, lastKn
ownMousePosition); |
| 349 } |
| 350 updateSelectionForMouseDrag(event.hitTestResult(), mousePressNode, dragStart
Pos, lastKnownMousePosition); |
| 351 } |
| 352 |
| 353 bool SelectionController::handleMouseReleaseEvent(const MouseEventWithHitTestRes
ults& event, const LayoutPoint& dragStartPos) |
| 354 { |
| 355 bool handled = false; |
| 356 m_mouseDownMayStartSelect = false; |
| 357 // Clear the selection if the mouse didn't move after the last mouse |
| 358 // press and it's not a context menu click. We do this so when clicking |
| 359 // on the selection, the selection goes away. However, if we are |
| 360 // editing, place the caret. |
| 361 if (m_singleClickInSelection && m_selectionState != SelectionState::Extended
Selection |
| 362 && dragStartPos == event.event().position() |
| 363 && selection().isRange() |
| 364 && event.event().button() != RightButton) { |
| 365 VisibleSelection newSelection; |
| 366 Node* node = event.innerNode(); |
| 367 bool caretBrowsing = m_frame->settings() && m_frame->settings()->caretBr
owsingEnabled(); |
| 368 if (node && node->layoutObject() && (caretBrowsing || node->hasEditableS
tyle())) { |
| 369 VisiblePosition pos = VisiblePosition(node->layoutObject()->position
ForPoint(event.localPoint())); |
| 370 newSelection = VisibleSelection(pos); |
| 371 } |
| 372 |
| 373 setSelectionIfNeeded(selection(), newSelection); |
| 374 |
| 375 handled = true; |
| 376 } |
| 377 |
| 378 selection().notifyLayoutObjectOfSelectionChange(UserTriggered); |
| 379 |
| 380 selection().selectFrameElementInParentIfFullySelected(); |
| 381 |
| 382 if (event.event().button() == MiddleButton && !event.isOverLink()) { |
| 383 // Ignore handled, since we want to paste to where the caret was placed
anyway. |
| 384 handled = handlePasteGlobalSelection(event.event()) || handled; |
| 385 } |
| 386 |
| 387 return handled; |
| 388 } |
| 389 |
| 390 bool SelectionController::handlePasteGlobalSelection(const PlatformMouseEvent& m
ouseEvent) |
| 391 { |
| 392 // If the event was a middle click, attempt to copy global selection in afte
r |
| 393 // the newly set caret position. |
| 394 // |
| 395 // This code is called from either the mouse up or mouse down handling. Ther
e |
| 396 // is some debate about when the global selection is pasted: |
| 397 // xterm: pastes on up. |
| 398 // GTK: pastes on down. |
| 399 // Qt: pastes on up. |
| 400 // Firefox: pastes on up. |
| 401 // Chromium: pastes on up. |
| 402 // |
| 403 // There is something of a webcompat angle to this well, as highlighted by |
| 404 // crbug.com/14608. Pages can clear text boxes 'onclick' and, if we paste on |
| 405 // down then the text is pasted just before the onclick handler runs and |
| 406 // clears the text box. So it's important this happens after the event |
| 407 // handlers have been fired. |
| 408 if (mouseEvent.type() != PlatformEvent::MouseReleased) |
| 409 return false; |
| 410 |
| 411 if (!m_frame->page()) |
| 412 return false; |
| 413 Frame* focusFrame = m_frame->page()->focusController().focusedOrMainFrame(); |
| 414 // Do not paste here if the focus was moved somewhere else. |
| 415 if (m_frame == focusFrame && m_frame->editor().behavior().supportsGlobalSele
ction()) |
| 416 return m_frame->editor().command("PasteGlobalSelection").execute(); |
| 417 |
| 418 return false; |
| 419 } |
| 420 |
| 421 bool SelectionController::handleGestureLongPress(const PlatformGestureEvent& ges
tureEvent, const HitTestResult& hitTestResult) |
| 422 { |
| 423 #if OS(ANDROID) |
| 424 bool shouldLongPressSelectWord = true; |
| 425 #else |
| 426 bool shouldLongPressSelectWord = m_frame->settings() && m_frame->settings()-
>touchEditingEnabled(); |
| 427 #endif |
| 428 if (!shouldLongPressSelectWord) |
| 429 return false; |
| 430 |
| 431 Node* innerNode = hitTestResult.innerNode(); |
| 432 if (!hitTestResult.isLiveLink() && innerNode && (innerNode->isContentEditabl
e() || innerNode->isTextNode() |
| 433 #if OS(ANDROID) |
| 434 || innerNode->canStartSelection() |
| 435 #endif |
| 436 )) { |
| 437 selectClosestWordFromHitTestResult(hitTestResult, AppendTrailingWhitespa
ce::DontAppend); |
| 438 if (m_frame->selection().isRange()) { |
| 439 Page* page = m_frame->page(); |
| 440 if (page) |
| 441 page->focusController().focusDocumentView(m_frame); |
| 442 |
| 443 return true; |
| 444 } |
| 445 } |
| 446 return false; |
| 447 } |
| 448 |
| 449 void SelectionController::updateSelectionForMouseDrag(Node* mousePressNode, cons
t LayoutPoint& dragStartPos, const IntPoint& lastKnownMousePosition) |
| 450 { |
| 451 FrameView* view = m_frame->view(); |
| 452 if (!view) |
| 453 return; |
| 454 LayoutView* layoutObject = m_frame->contentLayoutObject(); |
| 455 if (!layoutObject) |
| 456 return; |
| 457 |
| 458 HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | H
itTestRequest::Move); |
| 459 HitTestResult result(request, view->rootFrameToContents(lastKnownMousePositi
on)); |
| 460 layoutObject->hitTest(result); |
| 461 updateSelectionForMouseDrag(result, mousePressNode, dragStartPos, lastKnownM
ousePosition); |
| 462 } |
| 463 |
| 464 void SelectionController::updateSelectionForMouseDrag(const HitTestResult& hitTe
stResult, Node* mousePressNode, const LayoutPoint& dragStartPos, const IntPoint&
lastKnownMousePosition) |
| 465 { |
| 466 if (!m_mouseDownMayStartSelect) |
| 467 return; |
| 468 |
| 469 Node* target = hitTestResult.innerNode(); |
| 470 if (!target) |
| 471 return; |
| 472 |
| 473 VisiblePosition targetPosition = selection().selection().visiblePositionResp
ectingEditingBoundary(hitTestResult.localPoint(), target); |
| 474 // Don't modify the selection if we're not on a node. |
| 475 if (targetPosition.isNull()) |
| 476 return; |
| 477 |
| 478 // Restart the selection if this is the first mouse move. This work is usual
ly |
| 479 // done in handleMousePressEvent, but not if the mouse press was on an exist
ing selection. |
| 480 VisibleSelection newSelection = selection().selection(); |
| 481 |
| 482 // Special case to limit selection to the containing block for SVG text. |
| 483 // FIXME: Isn't there a better non-SVG-specific way to do this? |
| 484 if (Node* selectionBaseNode = newSelection.base().deprecatedNode()) { |
| 485 if (LayoutObject* selectionBaseLayoutObject = selectionBaseNode->layoutO
bject()) { |
| 486 if (selectionBaseLayoutObject->isSVGText()) { |
| 487 if (target->layoutObject()->containingBlock() != selectionBaseLa
youtObject->containingBlock()) |
| 488 return; |
| 489 } |
| 490 } |
| 491 } |
| 492 |
| 493 if (m_selectionState == SelectionState::HaveNotStartedSelection && !dispatch
SelectStart(target)) |
| 494 return; |
| 495 |
| 496 if (m_selectionState != SelectionState::ExtendedSelection) { |
| 497 // Always extend selection here because it's caused by a mouse drag |
| 498 m_selectionState = SelectionState::ExtendedSelection; |
| 499 newSelection = VisibleSelection(targetPosition); |
| 500 } |
| 501 |
| 502 if (RuntimeEnabledFeatures::userSelectAllEnabled()) { |
| 503 Node* rootUserSelectAllForMousePressNode = Position::rootUserSelectAllFo
rNode(mousePressNode); |
| 504 if (rootUserSelectAllForMousePressNode && rootUserSelectAllForMousePress
Node == Position::rootUserSelectAllForNode(target)) { |
| 505 newSelection.setBase(positionBeforeNode(rootUserSelectAllForMousePre
ssNode).upstream(CanCrossEditingBoundary)); |
| 506 newSelection.setExtent(positionAfterNode(rootUserSelectAllForMousePr
essNode).downstream(CanCrossEditingBoundary)); |
| 507 } else { |
| 508 // Reset base for user select all when base is inside user-select-al
l area and extent < base. |
| 509 if (rootUserSelectAllForMousePressNode && comparePositions(target->l
ayoutObject()->positionForPoint(hitTestResult.localPoint()), mousePressNode->lay
outObject()->positionForPoint(dragStartPos)) < 0) |
| 510 newSelection.setBase(positionAfterNode(rootUserSelectAllForMouse
PressNode).downstream(CanCrossEditingBoundary)); |
| 511 |
| 512 Node* rootUserSelectAllForTarget = Position::rootUserSelectAllForNod
e(target); |
| 513 if (rootUserSelectAllForTarget && mousePressNode->layoutObject() &&
comparePositions(target->layoutObject()->positionForPoint(hitTestResult.localPoi
nt()), mousePressNode->layoutObject()->positionForPoint(dragStartPos)) < 0) |
| 514 newSelection.setExtent(positionBeforeNode(rootUserSelectAllForTa
rget).upstream(CanCrossEditingBoundary)); |
| 515 else if (rootUserSelectAllForTarget && mousePressNode->layoutObject(
)) |
| 516 newSelection.setExtent(positionAfterNode(rootUserSelectAllForTar
get).downstream(CanCrossEditingBoundary)); |
| 517 else |
| 518 newSelection.setExtent(targetPosition); |
| 519 } |
| 520 } else { |
| 521 newSelection.setExtent(targetPosition); |
| 522 } |
| 523 |
| 524 if (selection().granularity() != CharacterGranularity) |
| 525 newSelection.expandUsingGranularity(selection().granularity()); |
| 526 |
| 527 selection().setNonDirectionalSelectionIfNeeded(newSelection, selection().gra
nularity(), |
| 528 FrameSelection::AdjustEndpointsAtBidiBoundary); |
| 529 } |
| 530 |
| 531 |
| 532 void SelectionController::prepareForContextMenu(const MouseEventWithHitTestResul
ts& mev, const LayoutPoint& position) |
| 533 { |
| 534 if (selection().contains(position) |
| 535 || mev.scrollbar() |
| 536 // FIXME: In the editable case, word selection sometimes selects content
that isn't underneath the mouse. |
| 537 // If the selection is non-editable, we do word selection to make it eas
ier to use the contextual menu items |
| 538 // available for text selections. But only if we're above text. |
| 539 || !(selection().isContentEditable() || (mev.innerNode() && mev.innerNod
e()->isTextNode()))) |
| 540 return; |
| 541 |
| 542 m_mouseDownMayStartSelect = true; // context menu events are always allowed
to perform a selection |
| 543 |
| 544 if (mev.hitTestResult().isMisspelled()) { |
| 545 selectClosestMisspellingFromMouseEvent(mev); |
| 546 return; |
| 547 } |
| 548 |
| 549 if (m_frame->editor().behavior().shouldSelectOnContextualMenuClick()) |
| 550 selectClosestWordOrLinkFromMouseEvent(mev); |
| 551 } |
| 552 |
| 553 void SelectionController::preparePassMousePressEventToSubframe(MouseEventWithHit
TestResults& mev) |
| 554 { |
| 555 // If we're clicking into a frame that is selected, the frame will appear |
| 556 // greyed out even though we're clicking on the selection. This looks |
| 557 // really strange (having the whole frame be greyed out), so we deselect the |
| 558 // selection. |
| 559 IntPoint p = m_frame->view()->rootFrameToContents(mev.event().position()); |
| 560 if (!selection().contains(p)) |
| 561 return; |
| 562 |
| 563 VisiblePosition visiblePos( |
| 564 mev.innerNode()->layoutObject()->positionForPoint(mev.localPoint())); |
| 565 VisibleSelection newSelection(visiblePos); |
| 566 selection().setSelection(newSelection); |
| 567 } |
| 568 |
| 569 void SelectionController::initializeSelectionState() |
| 570 { |
| 571 m_selectionState = SelectionState::HaveNotStartedSelection; |
| 572 } |
| 573 |
| 574 void SelectionController::setMouseDownMayStartSelect(bool mayStartSelect) |
| 575 { |
| 576 m_mouseDownMayStartSelect = mayStartSelect; |
| 577 } |
| 578 |
| 579 bool SelectionController::mouseDownMayStartSelect() const |
| 580 { |
| 581 return m_mouseDownMayStartSelect; |
| 582 } |
| 583 |
| 584 bool SelectionController::singleClickInSelection() const |
| 585 { |
| 586 return m_singleClickInSelection; |
| 587 } |
| 588 |
| 589 FrameSelection& SelectionController::selection() const |
| 590 { |
| 591 return m_frame->selection(); |
| 592 } |
| 593 |
| 594 } // namespace blink |
| OLD | NEW |