2 * Copyright (C) 2004, 2008, 2009, 2010 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 #include "core/editing/SelectionEditor.h"
29 #include "core/editing/EditingUtilities.h"
30 #include "core/editing/Editor.h"
31 #include "core/events/Event.h"
32 #include "core/frame/LocalFrame.h"
33 #include "core/frame/Settings.h"
34 #include "core/layout/LayoutBlock.h"
35 #include "core/layout/line/InlineTextBox.h"
36 #include "core/page/SpatialNavigation.h"
40 static inline LayoutUnit
NoXPosForVerticalArrowNavigation()
42 return LayoutUnit::min();
45 static inline bool shouldAlwaysUseDirectionalSelection(LocalFrame
* frame
)
47 return !frame
|| frame
->editor().behavior().shouldConsiderSelectionAsDirectional();
50 SelectionEditor::SelectionEditor(FrameSelection
& frameSelection
)
51 : m_frameSelection(frameSelection
)
52 , m_xPosForVerticalArrowNavigation(NoXPosForVerticalArrowNavigation())
53 , m_observingVisibleSelection(false)
57 SelectionEditor::~SelectionEditor()
60 // Oilpan: No need to clear out VisibleSelection observer;
61 // it is finalized as a part object of FrameSelection.
62 stopObservingVisibleSelectionChangeIfNecessary();
66 LocalFrame
* SelectionEditor::frame() const
68 return m_frameSelection
->frame();
71 void SelectionEditor::resetXPosForVerticalArrowNavigation()
73 m_xPosForVerticalArrowNavigation
= NoXPosForVerticalArrowNavigation();
76 void SelectionEditor::setIsDirectional(bool isDirectional
)
78 m_selection
.setIsDirectional(isDirectional
);
81 void SelectionEditor::setWithoutValidation(const Position
& start
, const Position
& end
)
83 m_selection
.setWithoutValidation(start
, end
);
86 TextDirection
SelectionEditor::directionOfEnclosingBlock()
88 return blink::directionOfEnclosingBlock(m_selection
.extent());
91 TextDirection
SelectionEditor::directionOfSelection()
93 InlineBox
* startBox
= nullptr;
94 InlineBox
* endBox
= nullptr;
95 // Cache the VisiblePositions because visibleStart() and visibleEnd()
96 // can cause layout, which has the potential to invalidate lineboxes.
97 VisiblePosition startPosition
= m_selection
.visibleStart();
98 VisiblePosition endPosition
= m_selection
.visibleEnd();
99 if (startPosition
.isNotNull())
100 startBox
= computeInlineBoxPosition(startPosition
).inlineBox
;
101 if (endPosition
.isNotNull())
102 endBox
= computeInlineBoxPosition(endPosition
).inlineBox
;
103 if (startBox
&& endBox
&& startBox
->direction() == endBox
->direction())
104 return startBox
->direction();
106 return directionOfEnclosingBlock();
109 void SelectionEditor::willBeModified(EAlteration alter
, SelectionDirection direction
)
111 if (alter
!= FrameSelection::AlterationExtend
)
114 Position start
= m_selection
.start();
115 Position end
= m_selection
.end();
117 bool baseIsStart
= true;
119 if (m_selection
.isDirectional()) {
120 // Make base and extent match start and end so we extend the user-visible selection.
121 // This only matters for cases where base and extend point to different positions than
122 // start and end (e.g. after a double-click to select a word).
123 if (m_selection
.isBaseFirst())
130 if (directionOfSelection() == LTR
)
135 case DirectionForward
:
139 if (directionOfSelection() == LTR
)
144 case DirectionBackward
:
150 m_selection
.setBase(start
);
151 m_selection
.setExtent(end
);
153 m_selection
.setBase(end
);
154 m_selection
.setExtent(start
);
158 VisiblePosition
SelectionEditor::positionForPlatform(bool isGetStart
) const
160 Settings
* settings
= frame() ? frame()->settings() : nullptr;
161 if (settings
&& settings
->editingBehaviorType() == EditingMacBehavior
)
162 return isGetStart
? m_selection
.visibleStart() : m_selection
.visibleEnd();
163 // Linux and Windows always extend selections from the extent endpoint.
164 // FIXME: VisibleSelection should be fixed to ensure as an invariant that
165 // base/extent always point to the same nodes as start/end, but which points
166 // to which depends on the value of isBaseFirst. Then this can be changed
167 // to just return m_sel.extent().
168 return m_selection
.isBaseFirst() ? m_selection
.visibleEnd() : m_selection
.visibleStart();
171 VisiblePosition
SelectionEditor::startForPlatform() const
173 return positionForPlatform(true);
176 VisiblePosition
SelectionEditor::endForPlatform() const
178 return positionForPlatform(false);
181 VisiblePosition
SelectionEditor::nextWordPositionForPlatform(const VisiblePosition
&originalPosition
)
183 VisiblePosition positionAfterCurrentWord
= nextWordPosition(originalPosition
);
185 if (frame() && frame()->editor().behavior().shouldSkipSpaceWhenMovingRight()) {
186 // In order to skip spaces when moving right, we advance one
187 // word further and then move one word back. Given the
188 // semantics of previousWordPosition() this will put us at the
189 // beginning of the word following.
190 VisiblePosition positionAfterSpacingAndFollowingWord
= nextWordPosition(positionAfterCurrentWord
);
191 if (positionAfterSpacingAndFollowingWord
.isNotNull() && positionAfterSpacingAndFollowingWord
.deepEquivalent() != positionAfterCurrentWord
.deepEquivalent())
192 positionAfterCurrentWord
= previousWordPosition(positionAfterSpacingAndFollowingWord
);
194 bool movingBackwardsMovedPositionToStartOfCurrentWord
= positionAfterCurrentWord
.deepEquivalent() == previousWordPosition(nextWordPosition(originalPosition
)).deepEquivalent();
195 if (movingBackwardsMovedPositionToStartOfCurrentWord
)
196 positionAfterCurrentWord
= positionAfterSpacingAndFollowingWord
;
198 return positionAfterCurrentWord
;
201 static void adjustPositionForUserSelectAll(VisiblePosition
& pos
, bool isForward
)
203 if (Node
* rootUserSelectAll
= EditingStrategy::rootUserSelectAllForNode(pos
.deepEquivalent().anchorNode()))
204 pos
= createVisiblePosition(isForward
? mostForwardCaretPosition(positionAfterNode(rootUserSelectAll
), CanCrossEditingBoundary
) : mostBackwardCaretPosition(positionBeforeNode(rootUserSelectAll
), CanCrossEditingBoundary
));
207 VisiblePosition
SelectionEditor::modifyExtendingRight(TextGranularity granularity
)
209 VisiblePosition pos
= createVisiblePosition(m_selection
.extent(), m_selection
.affinity());
211 // The difference between modifyExtendingRight and modifyExtendingForward is:
212 // modifyExtendingForward always extends forward logically.
213 // modifyExtendingRight behaves the same as modifyExtendingForward except for extending character or word,
214 // it extends forward logically if the enclosing block is LTR direction,
215 // but it extends backward logically if the enclosing block is RTL direction.
216 switch (granularity
) {
217 case CharacterGranularity
:
218 if (directionOfEnclosingBlock() == LTR
)
219 pos
= nextPositionOf(pos
, CanSkipOverEditingBoundary
);
221 pos
= previousPositionOf(pos
, CanSkipOverEditingBoundary
);
223 case WordGranularity
:
224 if (directionOfEnclosingBlock() == LTR
)
225 pos
= nextWordPositionForPlatform(pos
);
227 pos
= previousWordPosition(pos
);
230 if (directionOfEnclosingBlock() == LTR
)
231 pos
= modifyExtendingForward(granularity
);
233 pos
= modifyExtendingBackward(granularity
);
235 case SentenceGranularity
:
236 case LineGranularity
:
237 case ParagraphGranularity
:
238 case SentenceBoundary
:
239 case ParagraphBoundary
:
240 case DocumentBoundary
:
241 // FIXME: implement all of the above?
242 pos
= modifyExtendingForward(granularity
);
245 adjustPositionForUserSelectAll(pos
, directionOfEnclosingBlock() == LTR
);
249 VisiblePosition
SelectionEditor::modifyExtendingForward(TextGranularity granularity
)
251 VisiblePosition pos
= createVisiblePosition(m_selection
.extent(), m_selection
.affinity());
252 switch (granularity
) {
253 case CharacterGranularity
:
254 pos
= nextPositionOf(pos
, CanSkipOverEditingBoundary
);
256 case WordGranularity
:
257 pos
= nextWordPositionForPlatform(pos
);
259 case SentenceGranularity
:
260 pos
= nextSentencePosition(pos
);
262 case LineGranularity
:
263 pos
= nextLinePosition(pos
, lineDirectionPointForBlockDirectionNavigation(EXTENT
));
265 case ParagraphGranularity
:
266 pos
= nextParagraphPosition(pos
, lineDirectionPointForBlockDirectionNavigation(EXTENT
));
268 case SentenceBoundary
:
269 pos
= endOfSentence(endForPlatform());
272 pos
= logicalEndOfLine(endForPlatform());
274 case ParagraphBoundary
:
275 pos
= endOfParagraph(endForPlatform());
277 case DocumentBoundary
:
278 pos
= endForPlatform();
279 if (isEditablePosition(pos
.deepEquivalent()))
280 pos
= endOfEditableContent(pos
);
282 pos
= endOfDocument(pos
);
285 adjustPositionForUserSelectAll(pos
, directionOfEnclosingBlock() == LTR
);
289 VisiblePosition
SelectionEditor::modifyMovingRight(TextGranularity granularity
)
292 switch (granularity
) {
293 case CharacterGranularity
:
294 if (m_selection
.isRange()) {
295 if (directionOfSelection() == LTR
)
296 pos
= createVisiblePosition(m_selection
.end(), m_selection
.affinity());
298 pos
= createVisiblePosition(m_selection
.start(), m_selection
.affinity());
300 pos
= rightPositionOf(createVisiblePosition(m_selection
.extent(), m_selection
.affinity()));
303 case WordGranularity
: {
304 bool skipsSpaceWhenMovingRight
= frame() && frame()->editor().behavior().shouldSkipSpaceWhenMovingRight();
305 pos
= rightWordPosition(createVisiblePosition(m_selection
.extent(), m_selection
.affinity()), skipsSpaceWhenMovingRight
);
308 case SentenceGranularity
:
309 case LineGranularity
:
310 case ParagraphGranularity
:
311 case SentenceBoundary
:
312 case ParagraphBoundary
:
313 case DocumentBoundary
:
314 // FIXME: Implement all of the above.
315 pos
= modifyMovingForward(granularity
);
318 pos
= rightBoundaryOfLine(startForPlatform(), directionOfEnclosingBlock());
324 VisiblePosition
SelectionEditor::modifyMovingForward(TextGranularity granularity
)
327 // FIXME: Stay in editable content for the less common granularities.
328 switch (granularity
) {
329 case CharacterGranularity
:
330 if (m_selection
.isRange())
331 pos
= createVisiblePosition(m_selection
.end(), m_selection
.affinity());
333 pos
= nextPositionOf(createVisiblePosition(m_selection
.extent(), m_selection
.affinity()), CanSkipOverEditingBoundary
);
335 case WordGranularity
:
336 pos
= nextWordPositionForPlatform(createVisiblePosition(m_selection
.extent(), m_selection
.affinity()));
338 case SentenceGranularity
:
339 pos
= nextSentencePosition(createVisiblePosition(m_selection
.extent(), m_selection
.affinity()));
341 case LineGranularity
: {
342 // down-arrowing from a range selection that ends at the start of a line needs
343 // to leave the selection at that line start (no need to call nextLinePosition!)
344 pos
= endForPlatform();
345 if (!m_selection
.isRange() || !isStartOfLine(pos
))
346 pos
= nextLinePosition(pos
, lineDirectionPointForBlockDirectionNavigation(START
));
349 case ParagraphGranularity
:
350 pos
= nextParagraphPosition(endForPlatform(), lineDirectionPointForBlockDirectionNavigation(START
));
352 case SentenceBoundary
:
353 pos
= endOfSentence(endForPlatform());
356 pos
= logicalEndOfLine(endForPlatform());
358 case ParagraphBoundary
:
359 pos
= endOfParagraph(endForPlatform());
361 case DocumentBoundary
:
362 pos
= endForPlatform();
363 if (isEditablePosition(pos
.deepEquivalent()))
364 pos
= endOfEditableContent(pos
);
366 pos
= endOfDocument(pos
);
372 VisiblePosition
SelectionEditor::modifyExtendingLeft(TextGranularity granularity
)
374 VisiblePosition pos
= createVisiblePosition(m_selection
.extent(), m_selection
.affinity());
376 // The difference between modifyExtendingLeft and modifyExtendingBackward is:
377 // modifyExtendingBackward always extends backward logically.
378 // modifyExtendingLeft behaves the same as modifyExtendingBackward except for extending character or word,
379 // it extends backward logically if the enclosing block is LTR direction,
380 // but it extends forward logically if the enclosing block is RTL direction.
381 switch (granularity
) {
382 case CharacterGranularity
:
383 if (directionOfEnclosingBlock() == LTR
)
384 pos
= previousPositionOf(pos
, CanSkipOverEditingBoundary
);
386 pos
= nextPositionOf(pos
, CanSkipOverEditingBoundary
);
388 case WordGranularity
:
389 if (directionOfEnclosingBlock() == LTR
)
390 pos
= previousWordPosition(pos
);
392 pos
= nextWordPositionForPlatform(pos
);
395 if (directionOfEnclosingBlock() == LTR
)
396 pos
= modifyExtendingBackward(granularity
);
398 pos
= modifyExtendingForward(granularity
);
400 case SentenceGranularity
:
401 case LineGranularity
:
402 case ParagraphGranularity
:
403 case SentenceBoundary
:
404 case ParagraphBoundary
:
405 case DocumentBoundary
:
406 pos
= modifyExtendingBackward(granularity
);
409 adjustPositionForUserSelectAll(pos
, !(directionOfEnclosingBlock() == LTR
));
413 VisiblePosition
SelectionEditor::modifyExtendingBackward(TextGranularity granularity
)
415 VisiblePosition pos
= createVisiblePosition(m_selection
.extent(), m_selection
.affinity());
417 // Extending a selection backward by word or character from just after a table selects
418 // the table. This "makes sense" from the user perspective, esp. when deleting.
419 // It was done here instead of in VisiblePosition because we want VPs to iterate
421 switch (granularity
) {
422 case CharacterGranularity
:
423 pos
= previousPositionOf(pos
, CanSkipOverEditingBoundary
);
425 case WordGranularity
:
426 pos
= previousWordPosition(pos
);
428 case SentenceGranularity
:
429 pos
= previousSentencePosition(pos
);
431 case LineGranularity
:
432 pos
= previousLinePosition(pos
, lineDirectionPointForBlockDirectionNavigation(EXTENT
));
434 case ParagraphGranularity
:
435 pos
= previousParagraphPosition(pos
, lineDirectionPointForBlockDirectionNavigation(EXTENT
));
437 case SentenceBoundary
:
438 pos
= startOfSentence(startForPlatform());
441 pos
= logicalStartOfLine(startForPlatform());
443 case ParagraphBoundary
:
444 pos
= startOfParagraph(startForPlatform());
446 case DocumentBoundary
:
447 pos
= startForPlatform();
448 if (isEditablePosition(pos
.deepEquivalent()))
449 pos
= startOfEditableContent(pos
);
451 pos
= startOfDocument(pos
);
454 adjustPositionForUserSelectAll(pos
, !(directionOfEnclosingBlock() == LTR
));
458 VisiblePosition
SelectionEditor::modifyMovingLeft(TextGranularity granularity
)
461 switch (granularity
) {
462 case CharacterGranularity
:
463 if (m_selection
.isRange()) {
464 if (directionOfSelection() == LTR
)
465 pos
= createVisiblePosition(m_selection
.start(), m_selection
.affinity());
467 pos
= createVisiblePosition(m_selection
.end(), m_selection
.affinity());
469 pos
= leftPositionOf(createVisiblePosition(m_selection
.extent(), m_selection
.affinity()));
472 case WordGranularity
: {
473 bool skipsSpaceWhenMovingRight
= frame() && frame()->editor().behavior().shouldSkipSpaceWhenMovingRight();
474 pos
= leftWordPosition(createVisiblePosition(m_selection
.extent(), m_selection
.affinity()), skipsSpaceWhenMovingRight
);
477 case SentenceGranularity
:
478 case LineGranularity
:
479 case ParagraphGranularity
:
480 case SentenceBoundary
:
481 case ParagraphBoundary
:
482 case DocumentBoundary
:
483 // FIXME: Implement all of the above.
484 pos
= modifyMovingBackward(granularity
);
487 pos
= leftBoundaryOfLine(startForPlatform(), directionOfEnclosingBlock());
493 VisiblePosition
SelectionEditor::modifyMovingBackward(TextGranularity granularity
)
496 switch (granularity
) {
497 case CharacterGranularity
:
498 if (m_selection
.isRange())
499 pos
= createVisiblePosition(m_selection
.start(), m_selection
.affinity());
501 pos
= previousPositionOf(createVisiblePosition(m_selection
.extent(), m_selection
.affinity()), CanSkipOverEditingBoundary
);
503 case WordGranularity
:
504 pos
= previousWordPosition(createVisiblePosition(m_selection
.extent(), m_selection
.affinity()));
506 case SentenceGranularity
:
507 pos
= previousSentencePosition(createVisiblePosition(m_selection
.extent(), m_selection
.affinity()));
509 case LineGranularity
:
510 pos
= previousLinePosition(startForPlatform(), lineDirectionPointForBlockDirectionNavigation(START
));
512 case ParagraphGranularity
:
513 pos
= previousParagraphPosition(startForPlatform(), lineDirectionPointForBlockDirectionNavigation(START
));
515 case SentenceBoundary
:
516 pos
= startOfSentence(startForPlatform());
519 pos
= logicalStartOfLine(startForPlatform());
521 case ParagraphBoundary
:
522 pos
= startOfParagraph(startForPlatform());
524 case DocumentBoundary
:
525 pos
= startForPlatform();
526 if (isEditablePosition(pos
.deepEquivalent()))
527 pos
= startOfEditableContent(pos
);
529 pos
= startOfDocument(pos
);
535 static bool isBoundary(TextGranularity granularity
)
537 return granularity
== LineBoundary
|| granularity
== ParagraphBoundary
|| granularity
== DocumentBoundary
;
540 bool SelectionEditor::modify(EAlteration alter
, SelectionDirection direction
, TextGranularity granularity
, EUserTriggered userTriggered
)
542 if (userTriggered
== UserTriggered
) {
543 OwnPtrWillBeRawPtr
<FrameSelection
> trialFrameSelection
= FrameSelection::create();
544 trialFrameSelection
->setSelection(m_selection
);
545 trialFrameSelection
->modify(alter
, direction
, granularity
, NotUserTriggered
);
547 if (trialFrameSelection
->selection().isRange() && m_selection
.isCaret() && !dispatchSelectStart())
551 willBeModified(alter
, direction
);
553 bool wasRange
= m_selection
.isRange();
554 VisiblePosition originalStartPosition
= m_selection
.visibleStart();
555 VisiblePosition position
;
558 if (alter
== FrameSelection::AlterationMove
)
559 position
= modifyMovingRight(granularity
);
561 position
= modifyExtendingRight(granularity
);
563 case DirectionForward
:
564 if (alter
== FrameSelection::AlterationExtend
)
565 position
= modifyExtendingForward(granularity
);
567 position
= modifyMovingForward(granularity
);
570 if (alter
== FrameSelection::AlterationMove
)
571 position
= modifyMovingLeft(granularity
);
573 position
= modifyExtendingLeft(granularity
);
575 case DirectionBackward
:
576 if (alter
== FrameSelection::AlterationExtend
)
577 position
= modifyExtendingBackward(granularity
);
579 position
= modifyMovingBackward(granularity
);
583 if (position
.isNull())
586 if (isSpatialNavigationEnabled(frame())) {
587 if (!wasRange
&& alter
== FrameSelection::AlterationMove
&& position
.deepEquivalent() == originalStartPosition
.deepEquivalent())
591 // Some of the above operations set an xPosForVerticalArrowNavigation.
592 // Setting a selection will clear it, so save it to possibly restore later.
593 // Note: the START position type is arbitrary because it is unused, it would be
594 // the requested position type if there were no xPosForVerticalArrowNavigation set.
595 LayoutUnit x
= lineDirectionPointForBlockDirectionNavigation(START
);
596 m_selection
.setIsDirectional(shouldAlwaysUseDirectionalSelection(frame()) || alter
== FrameSelection::AlterationExtend
);
599 case FrameSelection::AlterationMove
:
600 m_frameSelection
->moveTo(position
, userTriggered
);
602 case FrameSelection::AlterationExtend
:
604 if (!m_selection
.isCaret()
605 && (granularity
== WordGranularity
|| granularity
== ParagraphGranularity
|| granularity
== LineGranularity
)
606 && frame() && !frame()->editor().behavior().shouldExtendSelectionByWordOrLineAcrossCaret()) {
607 // Don't let the selection go across the base position directly. Needed to match mac
608 // behavior when, for instance, word-selecting backwards starting with the caret in
609 // the middle of a word and then word-selecting forward, leaving the caret in the
610 // same place where it was, instead of directly selecting to the end of the word.
611 VisibleSelection newSelection
= m_selection
;
612 newSelection
.setExtent(position
);
613 if (m_selection
.isBaseFirst() != newSelection
.isBaseFirst())
614 position
= m_selection
.visibleBase();
617 // Standard Mac behavior when extending to a boundary is grow the
618 // selection rather than leaving the base in place and moving the
619 // extent. Matches NSTextView.
620 if (!frame() || !frame()->editor().behavior().shouldAlwaysGrowSelectionWhenExtendingToBoundary()
621 || m_selection
.isCaret()
622 || !isBoundary(granularity
)) {
623 m_frameSelection
->setExtent(position
, userTriggered
);
625 TextDirection textDirection
= directionOfEnclosingBlock();
626 if (direction
== DirectionForward
|| (textDirection
== LTR
&& direction
== DirectionRight
) || (textDirection
== RTL
&& direction
== DirectionLeft
))
627 m_frameSelection
->setEnd(position
, userTriggered
);
629 m_frameSelection
->setStart(position
, userTriggered
);
634 if (granularity
== LineGranularity
|| granularity
== ParagraphGranularity
)
635 m_xPosForVerticalArrowNavigation
= x
;
640 // FIXME: Maybe baseline would be better?
641 static bool absoluteCaretY(const VisiblePosition
&c
, int &y
)
643 IntRect rect
= absoluteCaretBoundsOf(c
);
646 y
= rect
.y() + rect
.height() / 2;
650 bool SelectionEditor::modify(EAlteration alter
, unsigned verticalDistance
, VerticalDirection direction
, EUserTriggered userTriggered
, CursorAlignOnScroll align
)
652 if (!verticalDistance
)
655 if (userTriggered
== UserTriggered
) {
656 OwnPtrWillBeRawPtr
<FrameSelection
> trialFrameSelection
= FrameSelection::create();
657 trialFrameSelection
->setSelection(m_selection
);
658 trialFrameSelection
->modify(alter
, verticalDistance
, direction
, NotUserTriggered
);
661 willBeModified(alter
, direction
== FrameSelection::DirectionUp
? DirectionBackward
: DirectionForward
);
666 case FrameSelection::AlterationMove
:
667 pos
= createVisiblePosition(direction
== FrameSelection::DirectionUp
? m_selection
.start() : m_selection
.end(), m_selection
.affinity());
668 xPos
= lineDirectionPointForBlockDirectionNavigation(direction
== FrameSelection::DirectionUp
? START
: END
);
669 m_selection
.setAffinity(direction
== FrameSelection::DirectionUp
? TextAffinity::Upstream
: TextAffinity::Downstream
);
671 case FrameSelection::AlterationExtend
:
672 pos
= createVisiblePosition(m_selection
.extent(), m_selection
.affinity());
673 xPos
= lineDirectionPointForBlockDirectionNavigation(EXTENT
);
674 m_selection
.setAffinity(TextAffinity::Downstream
);
679 if (!absoluteCaretY(pos
, startY
))
681 if (direction
== FrameSelection::DirectionUp
)
685 VisiblePosition result
;
686 VisiblePosition next
;
687 for (VisiblePosition p
= pos
; ; p
= next
) {
688 if (direction
== FrameSelection::DirectionUp
)
689 next
= previousLinePosition(p
, xPos
);
691 next
= nextLinePosition(p
, xPos
);
693 if (next
.isNull() || next
.deepEquivalent() == p
.deepEquivalent())
696 if (!absoluteCaretY(next
, nextY
))
698 if (direction
== FrameSelection::DirectionUp
)
700 if (nextY
- startY
> static_cast<int>(verticalDistance
))
702 if (nextY
>= lastY
) {
712 case FrameSelection::AlterationMove
:
713 m_frameSelection
->moveTo(result
, userTriggered
, align
);
715 case FrameSelection::AlterationExtend
:
716 m_frameSelection
->setExtent(result
, userTriggered
);
720 m_selection
.setIsDirectional(shouldAlwaysUseDirectionalSelection(frame()) || alter
== FrameSelection::AlterationExtend
);
725 // Abs x/y position of the caret ignoring transforms.
726 // TODO(yosin) navigation with transforms should be smarter.
727 static int lineDirectionPointForBlockDirectionNavigationOf(const VisiblePosition
& visiblePosition
)
729 if (visiblePosition
.isNull())
732 LayoutObject
* layoutObject
;
733 LayoutRect localRect
= localCaretRectOfPosition(visiblePosition
.toPositionWithAffinity(), layoutObject
);
734 if (localRect
.isEmpty() || !layoutObject
)
737 // This ignores transforms on purpose, for now. Vertical navigation is done
738 // without consulting transforms, so that 'up' in transformed text is 'up'
739 // relative to the text, not absolute 'up'.
740 FloatPoint caretPoint
= layoutObject
->localToAbsolute(FloatPoint(localRect
.location()));
741 LayoutObject
* containingBlock
= layoutObject
->containingBlock();
742 if (!containingBlock
)
743 containingBlock
= layoutObject
; // Just use ourselves to determine the writing mode if we have no containing block.
744 return containingBlock
->isHorizontalWritingMode() ? caretPoint
.x() : caretPoint
.y();
747 LayoutUnit
SelectionEditor::lineDirectionPointForBlockDirectionNavigation(EPositionType type
)
751 if (m_selection
.isNone())
757 pos
= m_selection
.start();
760 pos
= m_selection
.end();
763 pos
= m_selection
.base();
766 pos
= m_selection
.extent();
770 LocalFrame
* frame
= pos
.document()->frame();
774 if (m_xPosForVerticalArrowNavigation
== NoXPosForVerticalArrowNavigation()) {
775 VisiblePosition visiblePosition
= createVisiblePosition(pos
, m_selection
.affinity());
776 // VisiblePosition creation can fail here if a node containing the selection becomes visibility:hidden
777 // after the selection is created and before this function is called.
778 x
= lineDirectionPointForBlockDirectionNavigationOf(visiblePosition
);
779 m_xPosForVerticalArrowNavigation
= x
;
781 x
= m_xPosForVerticalArrowNavigation
;
787 bool SelectionEditor::setSelectedRange(const EphemeralRange
& range
, TextAffinity affinity
, SelectionDirectionalMode directional
, FrameSelection::SetSelectionOptions options
)
792 // Non-collapsed ranges are not allowed to start at the end of a line that is wrapped,
793 // they start at the beginning of the next line instead
794 m_logicalRange
= nullptr;
795 stopObservingVisibleSelectionChangeIfNecessary();
797 // Since |FrameSeleciton::setSelection()| dispatches events and DOM tree
798 // can be modified by event handlers, we should create |Range| object before
800 m_logicalRange
= createRange(range
);
802 VisibleSelection
newSelection(range
.startPosition(), range
.endPosition(), affinity
, directional
== SelectionDirectionalMode::Directional
);
803 m_frameSelection
->setSelection(newSelection
, options
);
804 startObservingVisibleSelectionChange();
808 PassRefPtrWillBeRawPtr
<Range
> SelectionEditor::firstRange() const
811 return m_logicalRange
->cloneRange();
812 return firstRangeOf(m_selection
);
815 bool SelectionEditor::dispatchSelectStart()
817 Node
* selectStartTarget
= m_selection
.extent().computeContainerNode();
818 if (!selectStartTarget
)
821 return selectStartTarget
->dispatchEvent(Event::createCancelableBubble(EventTypeNames::selectstart
));
824 void SelectionEditor::didChangeVisibleSelection()
826 ASSERT(m_observingVisibleSelection
);
827 // Invalidate the logical range when the underlying VisibleSelection has changed.
828 m_logicalRange
= nullptr;
829 m_selection
.clearChangeObserver();
830 m_observingVisibleSelection
= false;
833 void SelectionEditor::startObservingVisibleSelectionChange()
835 ASSERT(!m_observingVisibleSelection
);
836 m_selection
.setChangeObserver(*this);
837 m_observingVisibleSelection
= true;
840 void SelectionEditor::stopObservingVisibleSelectionChangeIfNecessary()
842 if (!m_observingVisibleSelection
)
844 m_selection
.clearChangeObserver();
845 m_observingVisibleSelection
= false;
848 DEFINE_TRACE(SelectionEditor
)
850 visitor
->trace(m_frameSelection
);
851 visitor
->trace(m_selection
);
852 visitor
->trace(m_logicalRange
);
853 VisibleSelectionChangeObserver::trace(visitor
);