blob: 4660cdc132d642e4a044dd681c3aa12748e9e3f5 [file] [log] [blame]
/*
* Copyright (C) 2004, 2008, 2009, 2010 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "core/editing/SelectionEditor.h"
#include "core/editing/EditingUtilities.h"
#include "core/editing/Editor.h"
#include "core/editing/SelectionAdjuster.h"
#include "core/events/Event.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/Settings.h"
#include "core/layout/LayoutBlock.h"
#include "core/layout/line/InlineTextBox.h"
#include "core/page/SpatialNavigation.h"
namespace blink {
static inline LayoutUnit NoXPosForVerticalArrowNavigation()
{
return LayoutUnit::min();
}
static inline bool shouldAlwaysUseDirectionalSelection(LocalFrame* frame)
{
return !frame || frame->editor().behavior().shouldConsiderSelectionAsDirectional();
}
SelectionEditor::SelectionEditor(FrameSelection& frameSelection)
: m_frameSelection(frameSelection)
, m_xPosForVerticalArrowNavigation(NoXPosForVerticalArrowNavigation())
, m_observingVisibleSelection(false)
{
}
SelectionEditor::~SelectionEditor()
{
#if !ENABLE(OILPAN)
// Oilpan: No need to clear out VisibleSelection observer;
// it is finalized as a part object of FrameSelection.
stopObservingVisibleSelectionChangeIfNecessary();
#endif
}
void SelectionEditor::dispose()
{
stopObservingVisibleSelectionChangeIfNecessary();
if (m_logicalRange) {
m_logicalRange->dispose();
m_logicalRange = nullptr;
}
}
LocalFrame* SelectionEditor::frame() const
{
return m_frameSelection->frame();
}
template <>
const VisibleSelection& SelectionEditor::visibleSelection<EditingStrategy>() const
{
return m_selection;
}
template <>
const VisibleSelectionInFlatTree& SelectionEditor::visibleSelection<EditingInFlatTreeStrategy>() const
{
return m_selectionInFlatTree;
}
void SelectionEditor::setVisibleSelection(const VisibleSelection& newSelection, FrameSelection::SetSelectionOptions options)
{
m_selection = newSelection;
if (options & FrameSelection::DoNotAdjustInFlatTree) {
m_selectionInFlatTree.setWithoutValidation(toPositionInFlatTree(m_selection.base()), toPositionInFlatTree(m_selection.extent()));
return;
}
SelectionAdjuster::adjustSelectionInFlatTree(&m_selectionInFlatTree, m_selection);
}
void SelectionEditor::setVisibleSelection(const VisibleSelectionInFlatTree& newSelection, FrameSelection::SetSelectionOptions options)
{
ASSERT(!(options & FrameSelection::DoNotAdjustInFlatTree));
m_selectionInFlatTree = newSelection;
SelectionAdjuster::adjustSelectionInDOMTree(&m_selection, m_selectionInFlatTree);
}
void SelectionEditor::resetXPosForVerticalArrowNavigation()
{
m_xPosForVerticalArrowNavigation = NoXPosForVerticalArrowNavigation();
}
void SelectionEditor::setIsDirectional(bool isDirectional)
{
m_selection.setIsDirectional(isDirectional);
m_selectionInFlatTree.setIsDirectional(isDirectional);
}
void SelectionEditor::setWithoutValidation(const Position& base, const Position& extent)
{
m_selection.setWithoutValidation(base, extent);
m_selectionInFlatTree.setWithoutValidation(toPositionInFlatTree(base), toPositionInFlatTree(extent));
}
TextDirection SelectionEditor::directionOfEnclosingBlock()
{
return blink::directionOfEnclosingBlock(m_selection.extent());
}
TextDirection SelectionEditor::directionOfSelection()
{
InlineBox* startBox = nullptr;
InlineBox* endBox = nullptr;
// Cache the VisiblePositions because visibleStart() and visibleEnd()
// can cause layout, which has the potential to invalidate lineboxes.
VisiblePosition startPosition = m_selection.visibleStart();
VisiblePosition endPosition = m_selection.visibleEnd();
if (startPosition.isNotNull())
startBox = computeInlineBoxPosition(startPosition).inlineBox;
if (endPosition.isNotNull())
endBox = computeInlineBoxPosition(endPosition).inlineBox;
if (startBox && endBox && startBox->direction() == endBox->direction())
return startBox->direction();
return directionOfEnclosingBlock();
}
void SelectionEditor::willBeModified(EAlteration alter, SelectionDirection direction)
{
if (alter != FrameSelection::AlterationExtend)
return;
Position start = m_selection.start();
Position end = m_selection.end();
bool baseIsStart = true;
if (m_selection.isDirectional()) {
// Make base and extent match start and end so we extend the user-visible selection.
// This only matters for cases where base and extend point to different positions than
// start and end (e.g. after a double-click to select a word).
if (m_selection.isBaseFirst())
baseIsStart = true;
else
baseIsStart = false;
} else {
switch (direction) {
case DirectionRight:
if (directionOfSelection() == LTR)
baseIsStart = true;
else
baseIsStart = false;
break;
case DirectionForward:
baseIsStart = true;
break;
case DirectionLeft:
if (directionOfSelection() == LTR)
baseIsStart = false;
else
baseIsStart = true;
break;
case DirectionBackward:
baseIsStart = false;
break;
}
}
if (baseIsStart) {
m_selection.setBase(start);
m_selection.setExtent(end);
} else {
m_selection.setBase(end);
m_selection.setExtent(start);
}
}
VisiblePosition SelectionEditor::positionForPlatform(bool isGetStart) const
{
Settings* settings = frame() ? frame()->settings() : nullptr;
if (settings && settings->editingBehaviorType() == EditingMacBehavior)
return isGetStart ? m_selection.visibleStart() : m_selection.visibleEnd();
// Linux and Windows always extend selections from the extent endpoint.
// FIXME: VisibleSelection should be fixed to ensure as an invariant that
// base/extent always point to the same nodes as start/end, but which points
// to which depends on the value of isBaseFirst. Then this can be changed
// to just return m_sel.extent().
return m_selection.isBaseFirst() ? m_selection.visibleEnd() : m_selection.visibleStart();
}
VisiblePosition SelectionEditor::startForPlatform() const
{
return positionForPlatform(true);
}
VisiblePosition SelectionEditor::endForPlatform() const
{
return positionForPlatform(false);
}
VisiblePosition SelectionEditor::nextWordPositionForPlatform(const VisiblePosition &originalPosition)
{
VisiblePosition positionAfterCurrentWord = nextWordPosition(originalPosition);
if (frame() && frame()->editor().behavior().shouldSkipSpaceWhenMovingRight()) {
// In order to skip spaces when moving right, we advance one
// word further and then move one word back. Given the
// semantics of previousWordPosition() this will put us at the
// beginning of the word following.
VisiblePosition positionAfterSpacingAndFollowingWord = nextWordPosition(positionAfterCurrentWord);
if (positionAfterSpacingAndFollowingWord.isNotNull() && positionAfterSpacingAndFollowingWord.deepEquivalent() != positionAfterCurrentWord.deepEquivalent())
positionAfterCurrentWord = previousWordPosition(positionAfterSpacingAndFollowingWord);
bool movingBackwardsMovedPositionToStartOfCurrentWord = positionAfterCurrentWord.deepEquivalent() == previousWordPosition(nextWordPosition(originalPosition)).deepEquivalent();
if (movingBackwardsMovedPositionToStartOfCurrentWord)
positionAfterCurrentWord = positionAfterSpacingAndFollowingWord;
}
return positionAfterCurrentWord;
}
static void adjustPositionForUserSelectAll(VisiblePosition& pos, bool isForward)
{
if (Node* rootUserSelectAll = EditingStrategy::rootUserSelectAllForNode(pos.deepEquivalent().anchorNode()))
pos = createVisiblePosition(isForward ? mostForwardCaretPosition(positionAfterNode(rootUserSelectAll), CanCrossEditingBoundary) : mostBackwardCaretPosition(positionBeforeNode(rootUserSelectAll), CanCrossEditingBoundary));
}
VisiblePosition SelectionEditor::modifyExtendingRight(TextGranularity granularity)
{
VisiblePosition pos = createVisiblePosition(m_selection.extent(), m_selection.affinity());
// The difference between modifyExtendingRight and modifyExtendingForward is:
// modifyExtendingForward always extends forward logically.
// modifyExtendingRight behaves the same as modifyExtendingForward except for extending character or word,
// it extends forward logically if the enclosing block is LTR direction,
// but it extends backward logically if the enclosing block is RTL direction.
switch (granularity) {
case CharacterGranularity:
if (directionOfEnclosingBlock() == LTR)
pos = nextPositionOf(pos, CanSkipOverEditingBoundary);
else
pos = previousPositionOf(pos, CanSkipOverEditingBoundary);
break;
case WordGranularity:
if (directionOfEnclosingBlock() == LTR)
pos = nextWordPositionForPlatform(pos);
else
pos = previousWordPosition(pos);
break;
case LineBoundary:
if (directionOfEnclosingBlock() == LTR)
pos = modifyExtendingForward(granularity);
else
pos = modifyExtendingBackward(granularity);
break;
case SentenceGranularity:
case LineGranularity:
case ParagraphGranularity:
case SentenceBoundary:
case ParagraphBoundary:
case DocumentBoundary:
// FIXME: implement all of the above?
pos = modifyExtendingForward(granularity);
break;
}
adjustPositionForUserSelectAll(pos, directionOfEnclosingBlock() == LTR);
return pos;
}
VisiblePosition SelectionEditor::modifyExtendingForward(TextGranularity granularity)
{
VisiblePosition pos = createVisiblePosition(m_selection.extent(), m_selection.affinity());
switch (granularity) {
case CharacterGranularity:
pos = nextPositionOf(pos, CanSkipOverEditingBoundary);
break;
case WordGranularity:
pos = nextWordPositionForPlatform(pos);
break;
case SentenceGranularity:
pos = nextSentencePosition(pos);
break;
case LineGranularity:
pos = nextLinePosition(pos, lineDirectionPointForBlockDirectionNavigation(EXTENT));
break;
case ParagraphGranularity:
pos = nextParagraphPosition(pos, lineDirectionPointForBlockDirectionNavigation(EXTENT));
break;
case SentenceBoundary:
pos = endOfSentence(endForPlatform());
break;
case LineBoundary:
pos = logicalEndOfLine(endForPlatform());
break;
case ParagraphBoundary:
pos = endOfParagraph(endForPlatform());
break;
case DocumentBoundary:
pos = endForPlatform();
if (isEditablePosition(pos.deepEquivalent()))
pos = endOfEditableContent(pos);
else
pos = endOfDocument(pos);
break;
}
adjustPositionForUserSelectAll(pos, directionOfEnclosingBlock() == LTR);
return pos;
}
VisiblePosition SelectionEditor::modifyMovingRight(TextGranularity granularity)
{
VisiblePosition pos;
switch (granularity) {
case CharacterGranularity:
if (m_selection.isRange()) {
if (directionOfSelection() == LTR)
pos = createVisiblePosition(m_selection.end(), m_selection.affinity());
else
pos = createVisiblePosition(m_selection.start(), m_selection.affinity());
} else {
pos = rightPositionOf(createVisiblePosition(m_selection.extent(), m_selection.affinity()));
}
break;
case WordGranularity: {
bool skipsSpaceWhenMovingRight = frame() && frame()->editor().behavior().shouldSkipSpaceWhenMovingRight();
pos = rightWordPosition(createVisiblePosition(m_selection.extent(), m_selection.affinity()), skipsSpaceWhenMovingRight);
break;
}
case SentenceGranularity:
case LineGranularity:
case ParagraphGranularity:
case SentenceBoundary:
case ParagraphBoundary:
case DocumentBoundary:
// FIXME: Implement all of the above.
pos = modifyMovingForward(granularity);
break;
case LineBoundary:
pos = rightBoundaryOfLine(startForPlatform(), directionOfEnclosingBlock());
break;
}
return pos;
}
VisiblePosition SelectionEditor::modifyMovingForward(TextGranularity granularity)
{
VisiblePosition pos;
// FIXME: Stay in editable content for the less common granularities.
switch (granularity) {
case CharacterGranularity:
if (m_selection.isRange())
pos = createVisiblePosition(m_selection.end(), m_selection.affinity());
else
pos = nextPositionOf(createVisiblePosition(m_selection.extent(), m_selection.affinity()), CanSkipOverEditingBoundary);
break;
case WordGranularity:
pos = nextWordPositionForPlatform(createVisiblePosition(m_selection.extent(), m_selection.affinity()));
break;
case SentenceGranularity:
pos = nextSentencePosition(createVisiblePosition(m_selection.extent(), m_selection.affinity()));
break;
case LineGranularity: {
// down-arrowing from a range selection that ends at the start of a line needs
// to leave the selection at that line start (no need to call nextLinePosition!)
pos = endForPlatform();
if (!m_selection.isRange() || !isStartOfLine(pos))
pos = nextLinePosition(pos, lineDirectionPointForBlockDirectionNavigation(START));
break;
}
case ParagraphGranularity:
pos = nextParagraphPosition(endForPlatform(), lineDirectionPointForBlockDirectionNavigation(START));
break;
case SentenceBoundary:
pos = endOfSentence(endForPlatform());
break;
case LineBoundary:
pos = logicalEndOfLine(endForPlatform());
break;
case ParagraphBoundary:
pos = endOfParagraph(endForPlatform());
break;
case DocumentBoundary:
pos = endForPlatform();
if (isEditablePosition(pos.deepEquivalent()))
pos = endOfEditableContent(pos);
else
pos = endOfDocument(pos);
break;
}
return pos;
}
VisiblePosition SelectionEditor::modifyExtendingLeft(TextGranularity granularity)
{
VisiblePosition pos = createVisiblePosition(m_selection.extent(), m_selection.affinity());
// The difference between modifyExtendingLeft and modifyExtendingBackward is:
// modifyExtendingBackward always extends backward logically.
// modifyExtendingLeft behaves the same as modifyExtendingBackward except for extending character or word,
// it extends backward logically if the enclosing block is LTR direction,
// but it extends forward logically if the enclosing block is RTL direction.
switch (granularity) {
case CharacterGranularity:
if (directionOfEnclosingBlock() == LTR)
pos = previousPositionOf(pos, CanSkipOverEditingBoundary);
else
pos = nextPositionOf(pos, CanSkipOverEditingBoundary);
break;
case WordGranularity:
if (directionOfEnclosingBlock() == LTR)
pos = previousWordPosition(pos);
else
pos = nextWordPositionForPlatform(pos);
break;
case LineBoundary:
if (directionOfEnclosingBlock() == LTR)
pos = modifyExtendingBackward(granularity);
else
pos = modifyExtendingForward(granularity);
break;
case SentenceGranularity:
case LineGranularity:
case ParagraphGranularity:
case SentenceBoundary:
case ParagraphBoundary:
case DocumentBoundary:
pos = modifyExtendingBackward(granularity);
break;
}
adjustPositionForUserSelectAll(pos, !(directionOfEnclosingBlock() == LTR));
return pos;
}
VisiblePosition SelectionEditor::modifyExtendingBackward(TextGranularity granularity)
{
VisiblePosition pos = createVisiblePosition(m_selection.extent(), m_selection.affinity());
// Extending a selection backward by word or character from just after a table selects
// the table. This "makes sense" from the user perspective, esp. when deleting.
// It was done here instead of in VisiblePosition because we want VPs to iterate
// over everything.
switch (granularity) {
case CharacterGranularity:
pos = previousPositionOf(pos, CanSkipOverEditingBoundary);
break;
case WordGranularity:
pos = previousWordPosition(pos);
break;
case SentenceGranularity:
pos = previousSentencePosition(pos);
break;
case LineGranularity:
pos = previousLinePosition(pos, lineDirectionPointForBlockDirectionNavigation(EXTENT));
break;
case ParagraphGranularity:
pos = previousParagraphPosition(pos, lineDirectionPointForBlockDirectionNavigation(EXTENT));
break;
case SentenceBoundary:
pos = startOfSentence(startForPlatform());
break;
case LineBoundary:
pos = logicalStartOfLine(startForPlatform());
break;
case ParagraphBoundary:
pos = startOfParagraph(startForPlatform());
break;
case DocumentBoundary:
pos = startForPlatform();
if (isEditablePosition(pos.deepEquivalent()))
pos = startOfEditableContent(pos);
else
pos = startOfDocument(pos);
break;
}
adjustPositionForUserSelectAll(pos, !(directionOfEnclosingBlock() == LTR));
return pos;
}
VisiblePosition SelectionEditor::modifyMovingLeft(TextGranularity granularity)
{
VisiblePosition pos;
switch (granularity) {
case CharacterGranularity:
if (m_selection.isRange()) {
if (directionOfSelection() == LTR)
pos = createVisiblePosition(m_selection.start(), m_selection.affinity());
else
pos = createVisiblePosition(m_selection.end(), m_selection.affinity());
} else {
pos = leftPositionOf(createVisiblePosition(m_selection.extent(), m_selection.affinity()));
}
break;
case WordGranularity: {
bool skipsSpaceWhenMovingRight = frame() && frame()->editor().behavior().shouldSkipSpaceWhenMovingRight();
pos = leftWordPosition(createVisiblePosition(m_selection.extent(), m_selection.affinity()), skipsSpaceWhenMovingRight);
break;
}
case SentenceGranularity:
case LineGranularity:
case ParagraphGranularity:
case SentenceBoundary:
case ParagraphBoundary:
case DocumentBoundary:
// FIXME: Implement all of the above.
pos = modifyMovingBackward(granularity);
break;
case LineBoundary:
pos = leftBoundaryOfLine(startForPlatform(), directionOfEnclosingBlock());
break;
}
return pos;
}
VisiblePosition SelectionEditor::modifyMovingBackward(TextGranularity granularity)
{
VisiblePosition pos;
switch (granularity) {
case CharacterGranularity:
if (m_selection.isRange())
pos = createVisiblePosition(m_selection.start(), m_selection.affinity());
else
pos = previousPositionOf(createVisiblePosition(m_selection.extent(), m_selection.affinity()), CanSkipOverEditingBoundary);
break;
case WordGranularity:
pos = previousWordPosition(createVisiblePosition(m_selection.extent(), m_selection.affinity()));
break;
case SentenceGranularity:
pos = previousSentencePosition(createVisiblePosition(m_selection.extent(), m_selection.affinity()));
break;
case LineGranularity:
pos = previousLinePosition(startForPlatform(), lineDirectionPointForBlockDirectionNavigation(START));
break;
case ParagraphGranularity:
pos = previousParagraphPosition(startForPlatform(), lineDirectionPointForBlockDirectionNavigation(START));
break;
case SentenceBoundary:
pos = startOfSentence(startForPlatform());
break;
case LineBoundary:
pos = logicalStartOfLine(startForPlatform());
break;
case ParagraphBoundary:
pos = startOfParagraph(startForPlatform());
break;
case DocumentBoundary:
pos = startForPlatform();
if (isEditablePosition(pos.deepEquivalent()))
pos = startOfEditableContent(pos);
else
pos = startOfDocument(pos);
break;
}
return pos;
}
static bool isBoundary(TextGranularity granularity)
{
return granularity == LineBoundary || granularity == ParagraphBoundary || granularity == DocumentBoundary;
}
bool SelectionEditor::modify(EAlteration alter, SelectionDirection direction, TextGranularity granularity, EUserTriggered userTriggered)
{
if (userTriggered == UserTriggered) {
OwnPtrWillBeRawPtr<FrameSelection> trialFrameSelection = FrameSelection::create();
trialFrameSelection->setSelection(m_selection);
trialFrameSelection->modify(alter, direction, granularity, NotUserTriggered);
if (trialFrameSelection->selection().isRange() && m_selection.isCaret() && !dispatchSelectStart())
return false;
}
willBeModified(alter, direction);
bool wasRange = m_selection.isRange();
VisiblePosition originalStartPosition = m_selection.visibleStart();
VisiblePosition position;
switch (direction) {
case DirectionRight:
if (alter == FrameSelection::AlterationMove)
position = modifyMovingRight(granularity);
else
position = modifyExtendingRight(granularity);
break;
case DirectionForward:
if (alter == FrameSelection::AlterationExtend)
position = modifyExtendingForward(granularity);
else
position = modifyMovingForward(granularity);
break;
case DirectionLeft:
if (alter == FrameSelection::AlterationMove)
position = modifyMovingLeft(granularity);
else
position = modifyExtendingLeft(granularity);
break;
case DirectionBackward:
if (alter == FrameSelection::AlterationExtend)
position = modifyExtendingBackward(granularity);
else
position = modifyMovingBackward(granularity);
break;
}
if (position.isNull())
return false;
if (isSpatialNavigationEnabled(frame())) {
if (!wasRange && alter == FrameSelection::AlterationMove && position.deepEquivalent() == originalStartPosition.deepEquivalent())
return false;
}
// Some of the above operations set an xPosForVerticalArrowNavigation.
// Setting a selection will clear it, so save it to possibly restore later.
// Note: the START position type is arbitrary because it is unused, it would be
// the requested position type if there were no xPosForVerticalArrowNavigation set.
LayoutUnit x = lineDirectionPointForBlockDirectionNavigation(START);
m_selection.setIsDirectional(shouldAlwaysUseDirectionalSelection(frame()) || alter == FrameSelection::AlterationExtend);
switch (alter) {
case FrameSelection::AlterationMove:
m_frameSelection->moveTo(position, userTriggered);
break;
case FrameSelection::AlterationExtend:
if (!m_selection.isCaret()
&& (granularity == WordGranularity || granularity == ParagraphGranularity || granularity == LineGranularity)
&& frame() && !frame()->editor().behavior().shouldExtendSelectionByWordOrLineAcrossCaret()) {
// Don't let the selection go across the base position directly. Needed to match mac
// behavior when, for instance, word-selecting backwards starting with the caret in
// the middle of a word and then word-selecting forward, leaving the caret in the
// same place where it was, instead of directly selecting to the end of the word.
VisibleSelection newSelection = m_selection;
newSelection.setExtent(position);
if (m_selection.isBaseFirst() != newSelection.isBaseFirst())
position = m_selection.visibleBase();
}
// Standard Mac behavior when extending to a boundary is grow the
// selection rather than leaving the base in place and moving the
// extent. Matches NSTextView.
if (!frame() || !frame()->editor().behavior().shouldAlwaysGrowSelectionWhenExtendingToBoundary()
|| m_selection.isCaret()
|| !isBoundary(granularity)) {
m_frameSelection->setExtent(position, userTriggered);
} else {
TextDirection textDirection = directionOfEnclosingBlock();
if (direction == DirectionForward || (textDirection == LTR && direction == DirectionRight) || (textDirection == RTL && direction == DirectionLeft))
m_frameSelection->setEnd(position, userTriggered);
else
m_frameSelection->setStart(position, userTriggered);
}
break;
}
if (granularity == LineGranularity || granularity == ParagraphGranularity)
m_xPosForVerticalArrowNavigation = x;
return true;
}
// FIXME: Maybe baseline would be better?
static bool absoluteCaretY(const VisiblePosition &c, int &y)
{
IntRect rect = absoluteCaretBoundsOf(c);
if (rect.isEmpty())
return false;
y = rect.y() + rect.height() / 2;
return true;
}
bool SelectionEditor::modify(EAlteration alter, unsigned verticalDistance, VerticalDirection direction, EUserTriggered userTriggered, CursorAlignOnScroll align)
{
if (!verticalDistance)
return false;
if (userTriggered == UserTriggered) {
OwnPtrWillBeRawPtr<FrameSelection> trialFrameSelection = FrameSelection::create();
trialFrameSelection->setSelection(m_selection);
trialFrameSelection->modify(alter, verticalDistance, direction, NotUserTriggered);
}
willBeModified(alter, direction == FrameSelection::DirectionUp ? DirectionBackward : DirectionForward);
VisiblePosition pos;
LayoutUnit xPos;
switch (alter) {
case FrameSelection::AlterationMove:
pos = createVisiblePosition(direction == FrameSelection::DirectionUp ? m_selection.start() : m_selection.end(), m_selection.affinity());
xPos = lineDirectionPointForBlockDirectionNavigation(direction == FrameSelection::DirectionUp ? START : END);
m_selection.setAffinity(direction == FrameSelection::DirectionUp ? TextAffinity::Upstream : TextAffinity::Downstream);
break;
case FrameSelection::AlterationExtend:
pos = createVisiblePosition(m_selection.extent(), m_selection.affinity());
xPos = lineDirectionPointForBlockDirectionNavigation(EXTENT);
m_selection.setAffinity(TextAffinity::Downstream);
break;
}
int startY;
if (!absoluteCaretY(pos, startY))
return false;
if (direction == FrameSelection::DirectionUp)
startY = -startY;
int lastY = startY;
VisiblePosition result;
VisiblePosition next;
for (VisiblePosition p = pos; ; p = next) {
if (direction == FrameSelection::DirectionUp)
next = previousLinePosition(p, xPos);
else
next = nextLinePosition(p, xPos);
if (next.isNull() || next.deepEquivalent() == p.deepEquivalent())
break;
int nextY;
if (!absoluteCaretY(next, nextY))
break;
if (direction == FrameSelection::DirectionUp)
nextY = -nextY;
if (nextY - startY > static_cast<int>(verticalDistance))
break;
if (nextY >= lastY) {
lastY = nextY;
result = next;
}
}
if (result.isNull())
return false;
switch (alter) {
case FrameSelection::AlterationMove:
m_frameSelection->moveTo(result, userTriggered, align);
break;
case FrameSelection::AlterationExtend:
m_frameSelection->setExtent(result, userTriggered);
break;
}
m_selection.setIsDirectional(shouldAlwaysUseDirectionalSelection(frame()) || alter == FrameSelection::AlterationExtend);
return true;
}
// Abs x/y position of the caret ignoring transforms.
// TODO(yosin) navigation with transforms should be smarter.
static LayoutUnit lineDirectionPointForBlockDirectionNavigationOf(const VisiblePosition& visiblePosition)
{
if (visiblePosition.isNull())
return LayoutUnit();
LayoutObject* layoutObject;
LayoutRect localRect = localCaretRectOfPosition(visiblePosition.toPositionWithAffinity(), layoutObject);
if (localRect.isEmpty() || !layoutObject)
return LayoutUnit();
// This ignores transforms on purpose, for now. Vertical navigation is done
// without consulting transforms, so that 'up' in transformed text is 'up'
// relative to the text, not absolute 'up'.
FloatPoint caretPoint = layoutObject->localToAbsolute(FloatPoint(localRect.location()));
LayoutObject* containingBlock = layoutObject->containingBlock();
if (!containingBlock)
containingBlock = layoutObject; // Just use ourselves to determine the writing mode if we have no containing block.
return LayoutUnit(containingBlock->isHorizontalWritingMode() ? caretPoint.x() : caretPoint.y());
}
LayoutUnit SelectionEditor::lineDirectionPointForBlockDirectionNavigation(EPositionType type)
{
LayoutUnit x;
if (m_selection.isNone())
return x;
Position pos;
switch (type) {
case START:
pos = m_selection.start();
break;
case END:
pos = m_selection.end();
break;
case BASE:
pos = m_selection.base();
break;
case EXTENT:
pos = m_selection.extent();
break;
}
LocalFrame* frame = pos.document()->frame();
if (!frame)
return x;
if (m_xPosForVerticalArrowNavigation == NoXPosForVerticalArrowNavigation()) {
VisiblePosition visiblePosition = createVisiblePosition(pos, m_selection.affinity());
// VisiblePosition creation can fail here if a node containing the selection becomes visibility:hidden
// after the selection is created and before this function is called.
x = lineDirectionPointForBlockDirectionNavigationOf(visiblePosition);
m_xPosForVerticalArrowNavigation = x;
} else {
x = m_xPosForVerticalArrowNavigation;
}
return x;
}
bool SelectionEditor::setSelectedRange(const EphemeralRange& range, TextAffinity affinity, SelectionDirectionalMode directional, FrameSelection::SetSelectionOptions options)
{
if (range.isNull())
return false;
// Non-collapsed ranges are not allowed to start at the end of a line that is wrapped,
// they start at the beginning of the next line instead
if (m_logicalRange) {
m_logicalRange->dispose();
m_logicalRange = nullptr;
}
stopObservingVisibleSelectionChangeIfNecessary();
// Since |FrameSeleciton::setSelection()| dispatches events and DOM tree
// can be modified by event handlers, we should create |Range| object before
// calling it.
m_logicalRange = createRange(range);
VisibleSelection newSelection(range.startPosition(), range.endPosition(), affinity, directional == SelectionDirectionalMode::Directional);
m_frameSelection->setSelection(newSelection, options);
startObservingVisibleSelectionChange();
return true;
}
PassRefPtrWillBeRawPtr<Range> SelectionEditor::firstRange() const
{
if (m_logicalRange)
return m_logicalRange->cloneRange();
return firstRangeOf(m_selection);
}
bool SelectionEditor::dispatchSelectStart()
{
Node* selectStartTarget = m_selection.extent().computeContainerNode();
if (!selectStartTarget)
return true;
return selectStartTarget->dispatchEvent(Event::createCancelableBubble(EventTypeNames::selectstart));
}
void SelectionEditor::didChangeVisibleSelection()
{
ASSERT(m_observingVisibleSelection);
// Invalidate the logical range when the underlying VisibleSelection has changed.
if (m_logicalRange) {
m_logicalRange->dispose();
m_logicalRange = nullptr;
}
m_selection.clearChangeObserver();
m_observingVisibleSelection = false;
}
void SelectionEditor::startObservingVisibleSelectionChange()
{
ASSERT(!m_observingVisibleSelection);
m_selection.setChangeObserver(*this);
m_observingVisibleSelection = true;
}
void SelectionEditor::stopObservingVisibleSelectionChangeIfNecessary()
{
if (!m_observingVisibleSelection)
return;
m_selection.clearChangeObserver();
m_observingVisibleSelection = false;
}
void SelectionEditor::updateIfNeeded()
{
m_selection.updateIfNeeded();
m_selectionInFlatTree.updateIfNeeded();
}
DEFINE_TRACE(SelectionEditor)
{
visitor->trace(m_frameSelection);
visitor->trace(m_selection);
visitor->trace(m_selectionInFlatTree);
visitor->trace(m_logicalRange);
VisibleSelectionChangeObserver::trace(visitor);
}
} // namespace blink