Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(621)

Side by Side Diff: Source/core/editing/SelectionController.cpp

Issue 1113323002: [Reland] Refactor the selection code in EventHandler (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
yosin_UTC9 2015/05/18 08:53:46 nit: Please use copyright comments in "EventHandle
Miyoung Shin(g) 2015/05/19 09:01:39 Done.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "config.h"
6 #include "core/editing/SelectionController.h"
7
8 #include "core/HTMLNames.h"
9 #include "core/dom/Document.h"
10 #include "core/dom/DocumentMarkerController.h"
11 #include "core/editing/Editor.h"
12 #include "core/editing/FrameSelection.h"
13 #include "core/editing/htmlediting.h"
14 #include "core/editing/iterators/TextIterator.h"
15 #include "core/events/Event.h"
16 #include "core/frame/FrameView.h"
17 #include "core/frame/LocalFrame.h"
18 #include "core/frame/Settings.h"
19 #include "core/layout/LayoutView.h"
20 #include "core/page/FocusController.h"
21 #include "core/page/Page.h"
22 #include "platform/RuntimeEnabledFeatures.h"
23
24 namespace blink {
25
26 static void setSelectionIfNeeded(FrameSelection& selection, const VisibleSelecti on& newSelection)
27 {
28 if (selection.selection() != newSelection)
29 selection.setSelection(newSelection);
30 }
31
32 static inline bool dispatchSelectStart(Node* node)
33 {
34 if (!node || !node->layoutObject())
35 return true;
36
37 return node->dispatchEvent(Event::createCancelableBubble(EventTypeNames::sel ectstart));
38 }
39
40 static VisibleSelection expandSelectionToRespectUserSelectAll(Node* targetNode, const VisibleSelection& selection)
41 {
42 Node* rootUserSelectAll = Position::rootUserSelectAllForNode(targetNode);
43 if (!rootUserSelectAll)
44 return selection;
45
46 VisibleSelection newSelection(selection);
47 newSelection.setBase(positionBeforeNode(rootUserSelectAll).upstream(CanCross EditingBoundary));
48 newSelection.setExtent(positionAfterNode(rootUserSelectAll).downstream(CanCr ossEditingBoundary));
49
50 return newSelection;
51 }
52
53 static inline bool canMouseDownStartSelect(Node* node)
54 {
55 if (!node || !node->layoutObject())
56 return true;
57
58 if (!node->canStartSelection())
59 return false;
60
61 return true;
62 }
63
64 static int textDistance(const Position& start, const Position& end)
65 {
66 RefPtrWillBeRawPtr<Range> range = Range::create(*start.document(), start, en d);
67 return TextIterator::rangeLength(range->startPosition(), range->endPosition( ), true);
68 }
69
70 SelectionController::SelectionController(LocalFrame* frame)
71 : m_frame(frame)
72 , m_allowSelection(false)
73 , m_singleClickInSelection(false)
74 , m_selectionState(HaveNotStartedSelection)
75 {
76 }
77
78 void SelectionController::clear()
79 {
80 m_allowSelection = false;
81 m_singleClickInSelection = false;
82 m_selectionState = HaveNotStartedSelection;
83
84 }
85
86 SelectionController::~SelectionController()
87 {
88 }
89
90 bool SelectionController::updateSelectionForMouseDownDispatchingSelectStart(Node * targetNode, const VisibleSelection& selection, TextGranularity granularity)
91 {
92 if (Position::nodeIsUserSelectNone(targetNode))
93 return false;
94
95 if (!dispatchSelectStart(targetNode))
96 return false;
97
98 if (selection.isRange()) {
99 m_selectionState = ExtendedSelection;
100 } else {
101 granularity = CharacterGranularity;
102 m_selectionState = PlacedCaret;
103 }
104
105 m_frame->selection().setNonDirectionalSelectionIfNeeded(selection, granulari ty);
yosin_UTC9 2015/05/18 08:53:46 Can we have private getter |selection()|? There ar
Miyoung Shin(g) 2015/05/19 09:01:39 Done.
106
107 return true;
108 }
109
110 void SelectionController::selectClosestWordFromHitTestResult(const HitTestResult & result, AppendTrailingWhitespace appendTrailingWhitespace)
111 {
112 Node* innerNode = result.innerNode();
113 VisibleSelection newSelection;
114
115 if (innerNode && innerNode->layoutObject()) {
116 VisiblePosition pos(innerNode->layoutObject()->positionForPoint(result.l ocalPoint()));
117 if (pos.isNotNull()) {
118 newSelection = VisibleSelection(pos);
119 newSelection.expandUsingGranularity(WordGranularity);
120 }
121
122 if (appendTrailingWhitespace == ShouldAppendTrailingWhitespace && newSel ection.isRange())
123 newSelection.appendTrailingWhitespace();
124
125 updateSelectionForMouseDownDispatchingSelectStart(innerNode, expandSelec tionToRespectUserSelectAll(innerNode, newSelection), WordGranularity);
126 }
127 }
128
129 void SelectionController::selectClosestMisspellingFromHitTestResult(const HitTes tResult& result, AppendTrailingWhitespace appendTrailingWhitespace)
130 {
131 Node* innerNode = result.innerNode();
132 VisibleSelection newSelection;
133
134 if (innerNode && innerNode->layoutObject()) {
135 VisiblePosition pos(innerNode->layoutObject()->positionForPoint(result.l ocalPoint()));
136 Position start = pos.deepEquivalent();
137 Position end = pos.deepEquivalent();
138 if (pos.isNotNull()) {
139 DocumentMarkerVector markers = innerNode->document().markers().marke rsInRange(makeRange(pos, pos).get(), DocumentMarker::MisspellingMarkers());
140 if (markers.size() == 1) {
141 start.moveToOffset(markers[0]->startOffset());
142 end.moveToOffset(markers[0]->endOffset());
143 newSelection = VisibleSelection(start, end);
144 }
145 }
146
147 if (appendTrailingWhitespace == ShouldAppendTrailingWhitespace && newSel ection.isRange())
148 newSelection.appendTrailingWhitespace();
149
150 updateSelectionForMouseDownDispatchingSelectStart(innerNode, expandSelec tionToRespectUserSelectAll(innerNode, newSelection), WordGranularity);
151 }
152 }
153
154 void SelectionController::selectClosestWordFromMouseEvent(const MouseEventWithHi tTestResults& result)
155 {
156 if (m_allowSelection) {
157 selectClosestWordFromHitTestResult(result.hitTestResult(),
158 (result.event().clickCount() == 2 && m_frame->editor().isSelectTrail ingWhitespaceEnabled()) ? ShouldAppendTrailingWhitespace : DontAppendTrailingWhi tespace);
159 }
160 }
161
162 void SelectionController::selectClosestMisspellingFromMouseEvent(const MouseEven tWithHitTestResults& result)
163 {
164 if (m_allowSelection) {
165 selectClosestMisspellingFromHitTestResult(result.hitTestResult(),
166 (result.event().clickCount() == 2 && m_frame->editor().isSelectTrail ingWhitespaceEnabled()) ? ShouldAppendTrailingWhitespace : DontAppendTrailingWhi tespace);
167 }
168 }
169
170 void SelectionController::selectClosestWordOrLinkFromMouseEvent(const MouseEvent WithHitTestResults& result)
171 {
172 if (!result.hitTestResult().isLiveLink())
173 return selectClosestWordFromMouseEvent(result);
174
175 Node* innerNode = result.innerNode();
176
177 if (innerNode && innerNode->layoutObject() && m_allowSelection) {
178 VisibleSelection newSelection;
179 Element* URLElement = result.hitTestResult().URLElement();
180 VisiblePosition pos(innerNode->layoutObject()->positionForPoint(result.l ocalPoint()));
181 if (pos.isNotNull() && pos.deepEquivalent().deprecatedNode()->isDescenda ntOf(URLElement))
182 newSelection = VisibleSelection::selectionFromContentsOfNode(URLElem ent);
183
184 updateSelectionForMouseDownDispatchingSelectStart(innerNode, expandSelec tionToRespectUserSelectAll(innerNode, newSelection), WordGranularity);
185 }
186 }
187
188 void SelectionController::handleMousePressEvent(const MouseEventWithHitTestResul ts& event)
189 {
190 // If we got the event back, that must mean it wasn't prevented,
191 // so it's allowed to start a drag or selection if it wasn't in a scrollbar.
192 m_allowSelection = canMouseDownStartSelect(event.innerNode()) && !event.scro llbar();
193 m_singleClickInSelection = false;
194 }
195
196 bool SelectionController::handleMousePressEventDoubleClick(const MouseEventWithH itTestResults& event)
197 {
198 TRACE_EVENT0("blink", "EventHandler::handleMousePressEventDoubleClick");
199
200 if (event.event().button() != LeftButton)
201 return false;
202
203 if (m_frame->selection().isRange()) {
204 // A double-click when range is already selected
205 // should not change the selection. So, do not call
206 // selectClosestWordFromMouseEvent, but do set
207 // m_beganSelectingText to prevent handleMouseReleaseEvent
208 // from setting caret selection.
209 m_selectionState = ExtendedSelection;
210 } else {
211 selectClosestWordFromMouseEvent(event);
212 }
213 return true;
214 }
215
216 bool SelectionController::handleMousePressEventTripleClick(const MouseEventWithH itTestResults& event)
217 {
218 TRACE_EVENT0("blink", "EventHandler::handleMousePressEventTripleClick");
219
220 if (event.event().button() != LeftButton)
221 return false;
222
223 Node* innerNode = event.innerNode();
224 if (!(innerNode && innerNode->layoutObject() && m_allowSelection))
225 return false;
226
227 VisibleSelection newSelection;
228 VisiblePosition pos(innerNode->layoutObject()->positionForPoint(event.localP oint()));
229 if (pos.isNotNull()) {
230 newSelection = VisibleSelection(pos);
231 newSelection.expandUsingGranularity(ParagraphGranularity);
232 }
233
234 return updateSelectionForMouseDownDispatchingSelectStart(innerNode, expandSe lectionToRespectUserSelectAll(innerNode, newSelection), ParagraphGranularity);
235 }
236
237 bool SelectionController::handleMousePressEventSingleClick(const MouseEventWithH itTestResults& event)
238 {
239 TRACE_EVENT0("blink", "EventHandler::handleMousePressEventSingleClick");
240
241 m_frame->document()->updateLayoutIgnorePendingStylesheets();
242 Node* innerNode = event.innerNode();
243 if (!(innerNode && innerNode->layoutObject() && m_allowSelection))
244 return false;
245
246 // Extend the selection if the Shift key is down, unless the click is in a l ink.
247 bool extendSelection = event.event().shiftKey() && !event.isOverLink();
248
249 // Don't restart the selection when the mouse is pressed on an
250 // existing selection so we can allow for text dragging.
251 if (FrameView* view = m_frame->view()) {
252 LayoutPoint vPoint = view->rootFrameToContents(event.event().position()) ;
253 if (!extendSelection && m_frame->selection().contains(vPoint)) {
254 m_singleClickInSelection = true;
255 return false;
256 }
257 }
258
259 VisiblePosition visiblePos(innerNode->layoutObject()->positionForPoint(event .localPoint()));
260 if (visiblePos.isNull())
261 visiblePos = VisiblePosition(firstPositionInOrBeforeNode(innerNode), DOW NSTREAM);
262 Position pos = visiblePos.deepEquivalent();
263
264 VisibleSelection newSelection = m_frame->selection().selection();
265 TextGranularity granularity = CharacterGranularity;
266
267 if (extendSelection && newSelection.isCaretOrRange()) {
268 VisibleSelection selectionInUserSelectAll(expandSelectionToRespectUserSe lectAll(innerNode, VisibleSelection(VisiblePosition(pos))));
269 if (selectionInUserSelectAll.isRange()) {
270 if (comparePositions(selectionInUserSelectAll.start(), newSelection. start()) < 0)
271 pos = selectionInUserSelectAll.start();
272 else if (comparePositions(newSelection.end(), selectionInUserSelectA ll.end()) < 0)
273 pos = selectionInUserSelectAll.end();
274 }
275
276 if (!m_frame->editor().behavior().shouldConsiderSelectionAsDirectional() ) {
277 if (pos.isNotNull()) {
278 // See <rdar://problem/3668157> REGRESSION (Mail): shift-click d eselects when selection
279 // was created right-to-left
280 Position start = newSelection.start();
281 Position end = newSelection.end();
282 int distanceToStart = textDistance(start, pos);
283 int distanceToEnd = textDistance(pos, end);
284 if (distanceToStart <= distanceToEnd)
285 newSelection = VisibleSelection(end, pos);
286 else
287 newSelection = VisibleSelection(start, pos);
288 }
289 } else {
290 newSelection.setExtent(pos);
291 }
292
293 if (m_frame->selection().granularity() != CharacterGranularity) {
294 granularity = m_frame->selection().granularity();
295 newSelection.expandUsingGranularity(m_frame->selection().granularity ());
296 }
297 } else if (m_selectionState != ExtendedSelection) {
298 newSelection = expandSelectionToRespectUserSelectAll(innerNode, VisibleS election(visiblePos));
299 }
300
301 // Updating the selection is considered side-effect of the event and so it d oesn't impact the handled state.
302 updateSelectionForMouseDownDispatchingSelectStart(innerNode, newSelection, g ranularity);
303 return false;
304 }
305
306 void SelectionController::handleMouseDraggedEvent(const MouseEventWithHitTestRes ults& event, const IntPoint& mouseDownPos, const LayoutPoint& dragStartPos, cons t RefPtr<Node> mousePressNode, const IntPoint& lastKnownMousePosition)
307 {
308 if (m_selectionState != ExtendedSelection) {
309 HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active );
310 HitTestResult result(request, mouseDownPos);
311 m_frame->document()->layoutView()->hitTest(result);
312
313 updateSelectionForMouseDrag(result, mousePressNode, dragStartPos, lastKn ownMousePosition);
314 }
315 updateSelectionForMouseDrag(event.hitTestResult(), mousePressNode, dragStart Pos, lastKnownMousePosition);
316 }
317
318 bool SelectionController::handleMouseReleaseEvent(const MouseEventWithHitTestRes ults& event, const LayoutPoint& dragStartPos)
319 {
320 bool handled = false;
321 m_allowSelection = false;
322 // Clear the selection if the mouse didn't move after the last mouse
323 // press and it's not a context menu click. We do this so when clicking
324 // on the selection, the selection goes away. However, if we are
325 // editing, place the caret.
326 if (m_singleClickInSelection && m_selectionState != ExtendedSelection
327 && dragStartPos == event.event().position()
328 && m_frame->selection().isRange()
329 && event.event().button() != RightButton) {
330 VisibleSelection newSelection;
331 Node* node = event.innerNode();
332 bool caretBrowsing = m_frame->settings() && m_frame->settings()->caretBr owsingEnabled();
333 if (node && node->layoutObject() && (caretBrowsing || node->hasEditableS tyle())) {
334 VisiblePosition pos = VisiblePosition(node->layoutObject()->position ForPoint(event.localPoint()));
335 newSelection = VisibleSelection(pos);
336 }
337
338 setSelectionIfNeeded(m_frame->selection(), newSelection);
339
340 handled = true;
341 }
342
343 m_frame->selection().notifyRendererOfSelectionChange(UserTriggered);
344
345 m_frame->selection().selectFrameElementInParentIfFullySelected();
346
347 if (event.event().button() == MiddleButton && !event.isOverLink()) {
348 // Ignore handled, since we want to paste to where the caret was placed anyway.
349 handled = handlePasteGlobalSelection(event.event()) || handled;
350 }
351
352 return handled;
353 }
354
355 bool SelectionController::handlePasteGlobalSelection(const PlatformMouseEvent& m ouseEvent)
356 {
357 // If the event was a middle click, attempt to copy global selection in afte r
358 // the newly set caret position.
359 //
360 // This code is called from either the mouse up or mouse down handling. Ther e
361 // is some debate about when the global selection is pasted:
362 // xterm: pastes on up.
363 // GTK: pastes on down.
364 // Qt: pastes on up.
365 // Firefox: pastes on up.
366 // Chromium: pastes on up.
367 //
368 // There is something of a webcompat angle to this well, as highlighted by
369 // crbug.com/14608. Pages can clear text boxes 'onclick' and, if we paste on
370 // down then the text is pasted just before the onclick handler runs and
371 // clears the text box. So it's important this happens after the event
372 // handlers have been fired.
373 if (mouseEvent.type() != PlatformEvent::MouseReleased)
374 return false;
375
376 if (!m_frame->page())
377 return false;
378 Frame* focusFrame = m_frame->page()->focusController().focusedOrMainFrame();
379 // Do not paste here if the focus was moved somewhere else.
380 if (m_frame == focusFrame && m_frame->editor().behavior().supportsGlobalSele ction())
381 return m_frame->editor().command("PasteGlobalSelection").execute();
382
383 return false;
384 }
385
386 void SelectionController::updateSelectionForMouseDrag(const RefPtr<Node> mousePr essNode, const LayoutPoint& dragStartPos, const IntPoint& lastKnownMousePosition )
387 {
388 FrameView* view = m_frame->view();
389 if (!view)
390 return;
391 LayoutView* renderer = m_frame->contentRenderer();
392 if (!renderer)
393 return;
394
395 HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | H itTestRequest::Move);
396 HitTestResult result(request, view->rootFrameToContents(lastKnownMousePositi on));
397 renderer->hitTest(result);
398 updateSelectionForMouseDrag(result, mousePressNode, dragStartPos, lastKnownM ousePosition);
399 }
400
401 void SelectionController::updateSelectionForMouseDrag(const HitTestResult& hitTe stResult, const RefPtr<Node> mousePressNode, const LayoutPoint& dragStartPos, co nst IntPoint& lastKnownMousePosition)
402 {
403 if (!m_allowSelection)
404 return;
405
406 Node* target = hitTestResult.innerNode();
407 if (!target)
408 return;
409
410 VisiblePosition targetPosition = m_frame->selection().selection().visiblePos itionRespectingEditingBoundary(hitTestResult.localPoint(), target);
411 // Don't modify the selection if we're not on a node.
412 if (targetPosition.isNull())
413 return;
414
415 // Restart the selection if this is the first mouse move. This work is usual ly
416 // done in handleMousePressEvent, but not if the mouse press was on an exist ing selection.
417 VisibleSelection newSelection = m_frame->selection().selection();
418
419 // Special case to limit selection to the containing block for SVG text.
420 // FIXME: Isn't there a better non-SVG-specific way to do this?
421 if (Node* selectionBaseNode = newSelection.base().deprecatedNode()) {
422 if (LayoutObject* selectionBaseRenderer = selectionBaseNode->layoutObjec t()) {
423 if (selectionBaseRenderer->isSVGText()) {
424 if (target->layoutObject()->containingBlock() != selectionBaseRe nderer->containingBlock())
425 return;
426 }
427 }
428 }
429
430 if (m_selectionState == HaveNotStartedSelection && !dispatchSelectStart(targ et))
431 return;
432
433 if (m_selectionState != ExtendedSelection) {
434 // Always extend selection here because it's caused by a mouse drag
435 m_selectionState = ExtendedSelection;
436 newSelection = VisibleSelection(targetPosition);
437 }
438
439 if (RuntimeEnabledFeatures::userSelectAllEnabled()) {
440 Node* rootUserSelectAllForMousePressNode = Position::rootUserSelectAllFo rNode(mousePressNode.get());
441 if (rootUserSelectAllForMousePressNode && rootUserSelectAllForMousePress Node == Position::rootUserSelectAllForNode(target)) {
442 newSelection.setBase(positionBeforeNode(rootUserSelectAllForMousePre ssNode).upstream(CanCrossEditingBoundary));
443 newSelection.setExtent(positionAfterNode(rootUserSelectAllForMousePr essNode).downstream(CanCrossEditingBoundary));
444 } else {
445 // Reset base for user select all when base is inside user-select-al l area and extent < base.
446 if (rootUserSelectAllForMousePressNode && comparePositions(target->l ayoutObject()->positionForPoint(hitTestResult.localPoint()), mousePressNode->lay outObject()->positionForPoint(dragStartPos)) < 0)
447 newSelection.setBase(positionAfterNode(rootUserSelectAllForMouse PressNode).downstream(CanCrossEditingBoundary));
448
449 Node* rootUserSelectAllForTarget = Position::rootUserSelectAllForNod e(target);
450 if (rootUserSelectAllForTarget && mousePressNode->layoutObject() && comparePositions(target->layoutObject()->positionForPoint(hitTestResult.localPoi nt()), mousePressNode->layoutObject()->positionForPoint(dragStartPos)) < 0)
451 newSelection.setExtent(positionBeforeNode(rootUserSelectAllForTa rget).upstream(CanCrossEditingBoundary));
452 else if (rootUserSelectAllForTarget && mousePressNode->layoutObject( ))
453 newSelection.setExtent(positionAfterNode(rootUserSelectAllForTar get).downstream(CanCrossEditingBoundary));
454 else
455 newSelection.setExtent(targetPosition);
456 }
457 } else {
458 newSelection.setExtent(targetPosition);
459 }
460
461 if (m_frame->selection().granularity() != CharacterGranularity)
462 newSelection.expandUsingGranularity(m_frame->selection().granularity());
463
464 m_frame->selection().setNonDirectionalSelectionIfNeeded(newSelection, m_fram e->selection().granularity(),
465 FrameSelection::AdjustEndpointsAtBidiBoundary);
466 }
467
468
469 void SelectionController::selectionForContextMenu(const MouseEventWithHitTestRes ults& mev, const LayoutPoint& position)
yosin_UTC9 2015/05/18 08:53:46 prepareForContextMenu? Please name functions to s
Miyoung Shin(g) 2015/05/19 09:01:42 Done. Thanks, I will try to name functions in fut
470 {
471 if (!m_frame->selection().contains(position)
yosin_UTC9 2015/05/18 08:53:46 nit: early return is better.
Miyoung Shin(g) 2015/05/19 09:01:39 Done.
472 && !mev.scrollbar()
473 // FIXME: In the editable case, word selection sometimes selects content that isn't underneath the mouse.
474 // If the selection is non-editable, we do word selection to make it eas ier to use the contextual menu items
475 // available for text selections. But only if we're above text.
476 && (m_frame->selection().isContentEditable() || (mev.innerNode() && mev. innerNode()->isTextNode()))) {
477 m_allowSelection = true; // context menu events are always allowed to pe rform a selection
478
479 if (mev.hitTestResult().isMisspelled())
480 selectClosestMisspellingFromMouseEvent(mev);
481 else if (m_frame->editor().behavior().shouldSelectOnContextualMenuClick( ))
482 selectClosestWordOrLinkFromMouseEvent(mev);
483 }
484 }
485
486 void SelectionController::releaseSelection(MouseEventWithHitTestResults& mev)
yosin_UTC9 2015/05/18 08:53:46 How about |preparePassMousePressEventToSubframe|?
Miyoung Shin(g) 2015/05/19 09:01:39 Done.
487 {
488 IntPoint p = m_frame->view()->rootFrameToContents(mev.event().position());
489 if (m_frame->selection().contains(p)) {
yosin_UTC9 2015/05/18 08:53:46 nit: early return is better.
Miyoung Shin(g) 2015/05/19 09:01:39 Done.
490 VisiblePosition visiblePos(
491 mev.innerNode()->layoutObject()->positionForPoint(mev.localPoint())) ;
492 VisibleSelection newSelection(visiblePos);
493 m_frame->selection().setSelection(newSelection);
494 }
495 }
496
497 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698