blob: 57dbb38ee7f74be217967fe113e58c6c2b01d9e5 [file] [log] [blame]
/*
* Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 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/VisibleUnits.h"
#include "core/HTMLNames.h"
#include "core/dom/Document.h"
#include "core/dom/Element.h"
#include "core/dom/FirstLetterPseudoElement.h"
#include "core/dom/NodeTraversal.h"
#include "core/dom/Text.h"
#include "core/editing/EditingUtilities.h"
#include "core/editing/FrameSelection.h"
#include "core/editing/Position.h"
#include "core/editing/PositionIterator.h"
#include "core/editing/RenderedPosition.h"
#include "core/editing/TextAffinity.h"
#include "core/editing/VisiblePosition.h"
#include "core/editing/iterators/BackwardsCharacterIterator.h"
#include "core/editing/iterators/BackwardsTextBuffer.h"
#include "core/editing/iterators/CharacterIterator.h"
#include "core/editing/iterators/ForwardsTextBuffer.h"
#include "core/editing/iterators/SimplifiedBackwardsTextIterator.h"
#include "core/editing/iterators/TextIterator.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/Settings.h"
#include "core/html/HTMLBRElement.h"
#include "core/html/TextControlElement.h"
#include "core/layout/HitTestRequest.h"
#include "core/layout/HitTestResult.h"
#include "core/layout/LayoutInline.h"
#include "core/layout/LayoutObject.h"
#include "core/layout/LayoutTextFragment.h"
#include "core/layout/LayoutView.h"
#include "core/layout/api/LayoutItem.h"
#include "core/layout/api/LayoutViewItem.h"
#include "core/layout/api/LineLayoutAPIShim.h"
#include "core/layout/api/LineLayoutItem.h"
#include "core/layout/line/InlineIterator.h"
#include "core/layout/line/InlineTextBox.h"
#include "platform/heap/Handle.h"
#include "platform/text/TextBoundaries.h"
#include "platform/text/TextBreakIterator.h"
namespace blink {
template <typename PositionType>
static PositionType canonicalizeCandidate(const PositionType& candidate) {
if (candidate.isNull())
return PositionType();
DCHECK(isVisuallyEquivalentCandidate(candidate));
PositionType upstream = mostBackwardCaretPosition(candidate);
if (isVisuallyEquivalentCandidate(upstream))
return upstream;
return candidate;
}
template <typename PositionType>
static PositionType canonicalPosition(const PositionType& passedPosition) {
// Sometimes updating selection positions can be extremely expensive and
// occur frequently. Often calling preventDefault on mousedown events can
// avoid doing unnecessary text selection work. http://crbug.com/472258.
TRACE_EVENT0("input", "VisibleUnits::canonicalPosition");
// The updateLayout call below can do so much that even the position passed
// in to us might get changed as a side effect. Specifically, there are code
// paths that pass selection endpoints, and updateLayout can change the
// selection.
PositionType position = passedPosition;
// FIXME (9535): Canonicalizing to the leftmost candidate means that if
// we're at a line wrap, we will ask layoutObjects to paint downstream
// carets for other layoutObjects. To fix this, we need to either a) add
// code to all paintCarets to pass the responsibility off to the appropriate
// layoutObject for VisiblePosition's like these, or b) canonicalize to the
// rightmost candidate unless the affinity is upstream.
if (position.isNull())
return PositionType();
DCHECK(position.document());
DCHECK(!position.document()->needsLayoutTreeUpdate());
Node* node = position.computeContainerNode();
PositionType candidate = mostBackwardCaretPosition(position);
if (isVisuallyEquivalentCandidate(candidate))
return candidate;
candidate = mostForwardCaretPosition(position);
if (isVisuallyEquivalentCandidate(candidate))
return candidate;
// When neither upstream or downstream gets us to a candidate
// (upstream/downstream won't leave blocks or enter new ones), we search
// forward and backward until we find one.
PositionType next = canonicalizeCandidate(nextCandidate(position));
PositionType prev = canonicalizeCandidate(previousCandidate(position));
Node* nextNode = next.anchorNode();
Node* prevNode = prev.anchorNode();
// The new position must be in the same editable element. Enforce that
// first. Unless the descent is from a non-editable html element to an
// editable body.
if (node && node->document().documentElement() == node &&
!hasEditableStyle(*node) && node->document().body() &&
hasEditableStyle(*node->document().body()))
return next.isNotNull() ? next : prev;
Element* editingRoot = rootEditableElementOf(position);
// If the html element is editable, descending into its body will look like
// a descent from non-editable to editable content since
// |rootEditableElementOf()| always stops at the body.
if ((editingRoot &&
editingRoot->document().documentElement() == editingRoot) ||
position.anchorNode()->isDocumentNode())
return next.isNotNull() ? next : prev;
bool prevIsInSameEditableElement =
prevNode && rootEditableElementOf(prev) == editingRoot;
bool nextIsInSameEditableElement =
nextNode && rootEditableElementOf(next) == editingRoot;
if (prevIsInSameEditableElement && !nextIsInSameEditableElement)
return prev;
if (nextIsInSameEditableElement && !prevIsInSameEditableElement)
return next;
if (!nextIsInSameEditableElement && !prevIsInSameEditableElement)
return PositionType();
// The new position should be in the same block flow element. Favor that.
Element* originalBlock = node ? enclosingBlockFlowElement(*node) : 0;
bool nextIsOutsideOriginalBlock =
!nextNode->isDescendantOf(originalBlock) && nextNode != originalBlock;
bool prevIsOutsideOriginalBlock =
!prevNode->isDescendantOf(originalBlock) && prevNode != originalBlock;
if (nextIsOutsideOriginalBlock && !prevIsOutsideOriginalBlock)
return prev;
return next;
}
Position canonicalPositionOf(const Position& position) {
return canonicalPosition(position);
}
PositionInFlatTree canonicalPositionOf(const PositionInFlatTree& position) {
return canonicalPosition(position);
}
template <typename Strategy>
static PositionWithAffinityTemplate<Strategy> honorEditingBoundaryAtOrBefore(
const PositionWithAffinityTemplate<Strategy>& pos,
const PositionTemplate<Strategy>& anchor) {
if (pos.isNull())
return pos;
ContainerNode* highestRoot = highestEditableRoot(anchor);
// Return empty position if |pos| is not somewhere inside the editable
// region containing this position
if (highestRoot && !pos.anchorNode()->isDescendantOf(highestRoot))
return PositionWithAffinityTemplate<Strategy>();
// Return |pos| itself if the two are from the very same editable region, or
// both are non-editable
// TODO(yosin) In the non-editable case, just because the new position is
// non-editable doesn't mean movement to it is allowed.
// |VisibleSelection::adjustForEditableContent()| has this problem too.
if (highestEditableRoot(pos.position()) == highestRoot)
return pos;
// Return empty position if this position is non-editable, but |pos| is
// editable.
// TODO(yosin) Move to the previous non-editable region.
if (!highestRoot)
return PositionWithAffinityTemplate<Strategy>();
// Return the last position before |pos| that is in the same editable region
// as this position
return lastEditablePositionBeforePositionInRoot(pos.position(), *highestRoot);
}
template <typename Strategy>
static VisiblePositionTemplate<Strategy> honorEditingBoundaryAtOrBefore(
const VisiblePositionTemplate<Strategy>& pos,
const PositionTemplate<Strategy>& anchor) {
DCHECK(pos.isValid()) << pos;
return createVisiblePosition(
honorEditingBoundaryAtOrBefore(pos.toPositionWithAffinity(), anchor));
}
template <typename Strategy>
static VisiblePositionTemplate<Strategy> honorEditingBoundaryAtOrAfter(
const VisiblePositionTemplate<Strategy>& pos,
const PositionTemplate<Strategy>& anchor) {
DCHECK(pos.isValid()) << pos;
if (pos.isNull())
return pos;
ContainerNode* highestRoot = highestEditableRoot(anchor);
// Return empty position if |pos| is not somewhere inside the editable
// region containing this position
if (highestRoot &&
!pos.deepEquivalent().anchorNode()->isDescendantOf(highestRoot))
return VisiblePositionTemplate<Strategy>();
// Return |pos| itself if the two are from the very same editable region, or
// both are non-editable
// TODO(yosin) In the non-editable case, just because the new position is
// non-editable doesn't mean movement to it is allowed.
// |VisibleSelection::adjustForEditableContent()| has this problem too.
if (highestEditableRoot(pos.deepEquivalent()) == highestRoot)
return pos;
// Return empty position if this position is non-editable, but |pos| is
// editable.
// TODO(yosin) Move to the next non-editable region.
if (!highestRoot)
return VisiblePositionTemplate<Strategy>();
// Return the next position after |pos| that is in the same editable region
// as this position
return firstEditableVisiblePositionAfterPositionInRoot(pos.deepEquivalent(),
*highestRoot);
}
static bool hasEditableStyle(const Node& node, EditableType editableType) {
if (editableType == HasEditableAXRole) {
if (AXObjectCache* cache = node.document().existingAXObjectCache()) {
if (cache->rootAXEditableElement(&node))
return true;
}
}
return hasEditableStyle(node);
}
static Element* rootEditableElement(const Node& node,
EditableType editableType) {
if (editableType == HasEditableAXRole) {
if (AXObjectCache* cache = node.document().existingAXObjectCache())
return const_cast<Element*>(cache->rootAXEditableElement(&node));
}
return rootEditableElement(node);
}
static Element* rootAXEditableElementOf(const Position& position) {
Node* node = position.computeContainerNode();
if (!node)
return 0;
if (isDisplayInsideTable(node))
node = node->parentNode();
return rootEditableElement(*node, HasEditableAXRole);
}
static bool hasAXEditableStyle(const Node& node) {
return hasEditableStyle(node, HasEditableAXRole);
}
static ContainerNode* highestEditableRoot(const Position& position,
EditableType editableType) {
if (editableType == HasEditableAXRole)
return highestEditableRoot(position, rootAXEditableElementOf,
hasAXEditableStyle);
return highestEditableRoot(position);
}
static Node* previousLeafWithSameEditability(Node* node,
EditableType editableType) {
bool editable = hasEditableStyle(*node, editableType);
node = previousAtomicLeafNode(*node);
while (node) {
if (editable == hasEditableStyle(*node, editableType))
return node;
node = previousAtomicLeafNode(*node);
}
return 0;
}
static Node* nextLeafWithSameEditability(
Node* node,
EditableType editableType = ContentIsEditable) {
if (!node)
return 0;
bool editable = hasEditableStyle(*node, editableType);
node = nextAtomicLeafNode(*node);
while (node) {
if (editable == hasEditableStyle(*node, editableType))
return node;
node = nextAtomicLeafNode(*node);
}
return 0;
}
// FIXME: consolidate with code in previousLinePosition.
static Position previousRootInlineBoxCandidatePosition(
Node* node,
const VisiblePosition& visiblePosition,
EditableType editableType) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
ContainerNode* highestRoot =
highestEditableRoot(visiblePosition.deepEquivalent(), editableType);
Node* previousNode = previousLeafWithSameEditability(node, editableType);
while (previousNode &&
(!previousNode->layoutObject() ||
inSameLine(
createVisiblePosition(firstPositionInOrBeforeNode(previousNode)),
visiblePosition)))
previousNode = previousLeafWithSameEditability(previousNode, editableType);
while (previousNode && !previousNode->isShadowRoot()) {
if (highestEditableRoot(firstPositionInOrBeforeNode(previousNode),
editableType) != highestRoot)
break;
Position pos = isHTMLBRElement(*previousNode)
? Position::beforeNode(previousNode)
: Position::editingPositionOf(
previousNode, caretMaxOffset(previousNode));
if (isVisuallyEquivalentCandidate(pos))
return pos;
previousNode = previousLeafWithSameEditability(previousNode, editableType);
}
return Position();
}
static Position nextRootInlineBoxCandidatePosition(
Node* node,
const VisiblePosition& visiblePosition,
EditableType editableType) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
ContainerNode* highestRoot =
highestEditableRoot(visiblePosition.deepEquivalent(), editableType);
Node* nextNode = nextLeafWithSameEditability(node, editableType);
while (nextNode && (!nextNode->layoutObject() ||
inSameLine(createVisiblePosition(
firstPositionInOrBeforeNode(nextNode)),
visiblePosition)))
nextNode = nextLeafWithSameEditability(nextNode, ContentIsEditable);
while (nextNode && !nextNode->isShadowRoot()) {
if (highestEditableRoot(firstPositionInOrBeforeNode(nextNode),
editableType) != highestRoot)
break;
Position pos;
pos = Position::editingPositionOf(nextNode, caretMinOffset(nextNode));
if (isVisuallyEquivalentCandidate(pos))
return pos;
nextNode = nextLeafWithSameEditability(nextNode, editableType);
}
return Position();
}
class CachedLogicallyOrderedLeafBoxes {
public:
CachedLogicallyOrderedLeafBoxes();
const InlineTextBox* previousTextBox(const RootInlineBox*,
const InlineTextBox*);
const InlineTextBox* nextTextBox(const RootInlineBox*, const InlineTextBox*);
size_t size() const { return m_leafBoxes.size(); }
const InlineBox* firstBox() const { return m_leafBoxes[0]; }
private:
const Vector<InlineBox*>& collectBoxes(const RootInlineBox*);
int boxIndexInLeaves(const InlineTextBox*) const;
const RootInlineBox* m_rootInlineBox;
Vector<InlineBox*> m_leafBoxes;
};
CachedLogicallyOrderedLeafBoxes::CachedLogicallyOrderedLeafBoxes()
: m_rootInlineBox(0) {}
const InlineTextBox* CachedLogicallyOrderedLeafBoxes::previousTextBox(
const RootInlineBox* root,
const InlineTextBox* box) {
if (!root)
return 0;
collectBoxes(root);
// If box is null, root is box's previous RootInlineBox, and previousBox is
// the last logical box in root.
int boxIndex = m_leafBoxes.size() - 1;
if (box)
boxIndex = boxIndexInLeaves(box) - 1;
for (int i = boxIndex; i >= 0; --i) {
if (m_leafBoxes[i]->isInlineTextBox())
return toInlineTextBox(m_leafBoxes[i]);
}
return 0;
}
const InlineTextBox* CachedLogicallyOrderedLeafBoxes::nextTextBox(
const RootInlineBox* root,
const InlineTextBox* box) {
if (!root)
return 0;
collectBoxes(root);
// If box is null, root is box's next RootInlineBox, and nextBox is the first
// logical box in root. Otherwise, root is box's RootInlineBox, and nextBox is
// the next logical box in the same line.
size_t nextBoxIndex = 0;
if (box)
nextBoxIndex = boxIndexInLeaves(box) + 1;
for (size_t i = nextBoxIndex; i < m_leafBoxes.size(); ++i) {
if (m_leafBoxes[i]->isInlineTextBox())
return toInlineTextBox(m_leafBoxes[i]);
}
return 0;
}
const Vector<InlineBox*>& CachedLogicallyOrderedLeafBoxes::collectBoxes(
const RootInlineBox* root) {
if (m_rootInlineBox != root) {
m_rootInlineBox = root;
m_leafBoxes.clear();
root->collectLeafBoxesInLogicalOrder(m_leafBoxes);
}
return m_leafBoxes;
}
int CachedLogicallyOrderedLeafBoxes::boxIndexInLeaves(
const InlineTextBox* box) const {
for (size_t i = 0; i < m_leafBoxes.size(); ++i) {
if (box == m_leafBoxes[i])
return i;
}
return 0;
}
static const InlineTextBox* logicallyPreviousBox(
const VisiblePosition& visiblePosition,
const InlineTextBox* textBox,
bool& previousBoxInDifferentBlock,
CachedLogicallyOrderedLeafBoxes& leafBoxes) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
const InlineBox* startBox = textBox;
const InlineTextBox* previousBox =
leafBoxes.previousTextBox(&startBox->root(), textBox);
if (previousBox)
return previousBox;
previousBox = leafBoxes.previousTextBox(startBox->root().prevRootBox(), 0);
if (previousBox)
return previousBox;
while (1) {
Node* startNode = startBox->getLineLayoutItem().nonPseudoNode();
if (!startNode)
break;
Position position = previousRootInlineBoxCandidatePosition(
startNode, visiblePosition, ContentIsEditable);
if (position.isNull())
break;
RenderedPosition renderedPosition(position, TextAffinity::Downstream);
RootInlineBox* previousRoot = renderedPosition.rootBox();
if (!previousRoot)
break;
previousBox = leafBoxes.previousTextBox(previousRoot, 0);
if (previousBox) {
previousBoxInDifferentBlock = true;
return previousBox;
}
if (!leafBoxes.size())
break;
startBox = leafBoxes.firstBox();
}
return 0;
}
static const InlineTextBox* logicallyNextBox(
const VisiblePosition& visiblePosition,
const InlineTextBox* textBox,
bool& nextBoxInDifferentBlock,
CachedLogicallyOrderedLeafBoxes& leafBoxes) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
const InlineBox* startBox = textBox;
const InlineTextBox* nextBox =
leafBoxes.nextTextBox(&startBox->root(), textBox);
if (nextBox)
return nextBox;
nextBox = leafBoxes.nextTextBox(startBox->root().nextRootBox(), 0);
if (nextBox)
return nextBox;
while (1) {
Node* startNode = startBox->getLineLayoutItem().nonPseudoNode();
if (!startNode)
break;
Position position = nextRootInlineBoxCandidatePosition(
startNode, visiblePosition, ContentIsEditable);
if (position.isNull())
break;
RenderedPosition renderedPosition(position, TextAffinity::Downstream);
RootInlineBox* nextRoot = renderedPosition.rootBox();
if (!nextRoot)
break;
nextBox = leafBoxes.nextTextBox(nextRoot, 0);
if (nextBox) {
nextBoxInDifferentBlock = true;
return nextBox;
}
if (!leafBoxes.size())
break;
startBox = leafBoxes.firstBox();
}
return 0;
}
static TextBreakIterator* wordBreakIteratorForMinOffsetBoundary(
const VisiblePosition& visiblePosition,
const InlineTextBox* textBox,
int& previousBoxLength,
bool& previousBoxInDifferentBlock,
Vector<UChar, 1024>& string,
CachedLogicallyOrderedLeafBoxes& leafBoxes) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
previousBoxInDifferentBlock = false;
// FIXME: Handle the case when we don't have an inline text box.
const InlineTextBox* previousBox = logicallyPreviousBox(
visiblePosition, textBox, previousBoxInDifferentBlock, leafBoxes);
int len = 0;
string.clear();
if (previousBox) {
previousBoxLength = previousBox->len();
previousBox->getLineLayoutItem().text().appendTo(
string, previousBox->start(), previousBoxLength);
len += previousBoxLength;
}
textBox->getLineLayoutItem().text().appendTo(string, textBox->start(),
textBox->len());
len += textBox->len();
return wordBreakIterator(string.data(), len);
}
static TextBreakIterator* wordBreakIteratorForMaxOffsetBoundary(
const VisiblePosition& visiblePosition,
const InlineTextBox* textBox,
bool& nextBoxInDifferentBlock,
Vector<UChar, 1024>& string,
CachedLogicallyOrderedLeafBoxes& leafBoxes) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
nextBoxInDifferentBlock = false;
// FIXME: Handle the case when we don't have an inline text box.
const InlineTextBox* nextBox = logicallyNextBox(
visiblePosition, textBox, nextBoxInDifferentBlock, leafBoxes);
int len = 0;
string.clear();
textBox->getLineLayoutItem().text().appendTo(string, textBox->start(),
textBox->len());
len += textBox->len();
if (nextBox) {
nextBox->getLineLayoutItem().text().appendTo(string, nextBox->start(),
nextBox->len());
len += nextBox->len();
}
return wordBreakIterator(string.data(), len);
}
static bool isLogicalStartOfWord(TextBreakIterator* iter,
int position,
bool hardLineBreak) {
bool boundary = hardLineBreak ? true : iter->isBoundary(position);
if (!boundary)
return false;
iter->following(position);
// isWordTextBreak returns true after moving across a word and false after
// moving across a punctuation/space.
return isWordTextBreak(iter);
}
static bool islogicalEndOfWord(TextBreakIterator* iter,
int position,
bool hardLineBreak) {
bool boundary = iter->isBoundary(position);
return (hardLineBreak || boundary) && isWordTextBreak(iter);
}
enum CursorMovementDirection { MoveLeft, MoveRight };
static VisiblePosition visualWordPosition(
const VisiblePosition& visiblePosition,
CursorMovementDirection direction,
bool skipsSpaceWhenMovingRight) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
if (visiblePosition.isNull())
return VisiblePosition();
TextDirection blockDirection =
directionOfEnclosingBlock(visiblePosition.deepEquivalent());
InlineBox* previouslyVisitedBox = 0;
VisiblePosition current = visiblePosition;
TextBreakIterator* iter = 0;
CachedLogicallyOrderedLeafBoxes leafBoxes;
Vector<UChar, 1024> string;
while (1) {
VisiblePosition adjacentCharacterPosition = direction == MoveRight
? rightPositionOf(current)
: leftPositionOf(current);
if (adjacentCharacterPosition.deepEquivalent() ==
current.deepEquivalent() ||
adjacentCharacterPosition.isNull())
return VisiblePosition();
InlineBoxPosition boxPosition = computeInlineBoxPosition(
adjacentCharacterPosition.deepEquivalent(), TextAffinity::Upstream);
InlineBox* box = boxPosition.inlineBox;
int offsetInBox = boxPosition.offsetInBox;
if (!box)
break;
if (!box->isInlineTextBox()) {
current = adjacentCharacterPosition;
continue;
}
InlineTextBox* textBox = toInlineTextBox(box);
int previousBoxLength = 0;
bool previousBoxInDifferentBlock = false;
bool nextBoxInDifferentBlock = false;
bool movingIntoNewBox = previouslyVisitedBox != box;
if (offsetInBox == box->caretMinOffset()) {
iter = wordBreakIteratorForMinOffsetBoundary(
visiblePosition, textBox, previousBoxLength,
previousBoxInDifferentBlock, string, leafBoxes);
} else if (offsetInBox == box->caretMaxOffset()) {
iter = wordBreakIteratorForMaxOffsetBoundary(
visiblePosition, textBox, nextBoxInDifferentBlock, string, leafBoxes);
} else if (movingIntoNewBox) {
iter = wordBreakIterator(textBox->getLineLayoutItem().text(),
textBox->start(), textBox->len());
previouslyVisitedBox = box;
}
if (!iter)
break;
iter->first();
int offsetInIterator = offsetInBox - textBox->start() + previousBoxLength;
bool isWordBreak;
bool boxHasSameDirectionalityAsBlock = box->direction() == blockDirection;
bool movingBackward =
(direction == MoveLeft && box->direction() == TextDirection::Ltr) ||
(direction == MoveRight && box->direction() == TextDirection::Rtl);
if ((skipsSpaceWhenMovingRight && boxHasSameDirectionalityAsBlock) ||
(!skipsSpaceWhenMovingRight && movingBackward)) {
bool logicalStartInLayoutObject =
offsetInBox == static_cast<int>(textBox->start()) &&
previousBoxInDifferentBlock;
isWordBreak = isLogicalStartOfWord(iter, offsetInIterator,
logicalStartInLayoutObject);
} else {
bool logicalEndInLayoutObject =
offsetInBox == static_cast<int>(textBox->start() + textBox->len()) &&
nextBoxInDifferentBlock;
isWordBreak =
islogicalEndOfWord(iter, offsetInIterator, logicalEndInLayoutObject);
}
if (isWordBreak)
return adjacentCharacterPosition;
current = adjacentCharacterPosition;
}
return VisiblePosition();
}
VisiblePosition leftWordPosition(const VisiblePosition& visiblePosition,
bool skipsSpaceWhenMovingRight) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
VisiblePosition leftWordBreak =
visualWordPosition(visiblePosition, MoveLeft, skipsSpaceWhenMovingRight);
leftWordBreak = honorEditingBoundaryAtOrBefore(
leftWordBreak, visiblePosition.deepEquivalent());
// FIXME: How should we handle a non-editable position?
if (leftWordBreak.isNull() &&
isEditablePosition(visiblePosition.deepEquivalent())) {
TextDirection blockDirection =
directionOfEnclosingBlock(visiblePosition.deepEquivalent());
leftWordBreak = blockDirection == TextDirection::Ltr
? startOfEditableContent(visiblePosition)
: endOfEditableContent(visiblePosition);
}
return leftWordBreak;
}
VisiblePosition rightWordPosition(const VisiblePosition& visiblePosition,
bool skipsSpaceWhenMovingRight) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
VisiblePosition rightWordBreak =
visualWordPosition(visiblePosition, MoveRight, skipsSpaceWhenMovingRight);
rightWordBreak = honorEditingBoundaryAtOrBefore(
rightWordBreak, visiblePosition.deepEquivalent());
// FIXME: How should we handle a non-editable position?
if (rightWordBreak.isNull() &&
isEditablePosition(visiblePosition.deepEquivalent())) {
TextDirection blockDirection =
directionOfEnclosingBlock(visiblePosition.deepEquivalent());
rightWordBreak = blockDirection == TextDirection::Ltr
? endOfEditableContent(visiblePosition)
: startOfEditableContent(visiblePosition);
}
return rightWordBreak;
}
template <typename Strategy>
static ContainerNode* nonShadowBoundaryParentNode(Node* node) {
ContainerNode* parent = Strategy::parent(*node);
return parent && !parent->isShadowRoot() ? parent : nullptr;
}
template <typename Strategy>
static Node* parentEditingBoundary(const PositionTemplate<Strategy>& position) {
Node* const anchorNode = position.anchorNode();
if (!anchorNode)
return nullptr;
Node* documentElement = anchorNode->document().documentElement();
if (!documentElement)
return nullptr;
Node* boundary = position.computeContainerNode();
while (boundary != documentElement &&
nonShadowBoundaryParentNode<Strategy>(boundary) &&
hasEditableStyle(*anchorNode) ==
hasEditableStyle(*Strategy::parent(*boundary)))
boundary = nonShadowBoundaryParentNode<Strategy>(boundary);
return boundary;
}
enum BoundarySearchContextAvailability {
DontHaveMoreContext,
MayHaveMoreContext
};
typedef unsigned (*BoundarySearchFunction)(const UChar*,
unsigned length,
unsigned offset,
BoundarySearchContextAvailability,
bool& needMoreContext);
template <typename Strategy>
static VisiblePositionTemplate<Strategy> previousBoundary(
const VisiblePositionTemplate<Strategy>& c,
BoundarySearchFunction searchFunction) {
DCHECK(c.isValid()) << c;
const PositionTemplate<Strategy> pos = c.deepEquivalent();
Node* boundary = parentEditingBoundary(pos);
if (!boundary)
return VisiblePositionTemplate<Strategy>();
const PositionTemplate<Strategy> start =
PositionTemplate<Strategy>::editingPositionOf(boundary, 0)
.parentAnchoredEquivalent();
const PositionTemplate<Strategy> end = pos.parentAnchoredEquivalent();
ForwardsTextBuffer suffixString;
if (requiresContextForWordBoundary(characterBefore(c))) {
TextIteratorAlgorithm<Strategy> forwardsIterator(
end, PositionTemplate<Strategy>::afterNode(boundary));
while (!forwardsIterator.atEnd()) {
forwardsIterator.copyTextTo(&suffixString);
int contextEndIndex = endOfFirstWordBoundaryContext(
suffixString.data() + suffixString.size() - forwardsIterator.length(),
forwardsIterator.length());
if (contextEndIndex < forwardsIterator.length()) {
suffixString.shrink(forwardsIterator.length() - contextEndIndex);
break;
}
forwardsIterator.advance();
}
}
unsigned suffixLength = suffixString.size();
BackwardsTextBuffer string;
string.pushRange(suffixString.data(), suffixString.size());
SimplifiedBackwardsTextIteratorAlgorithm<Strategy> it(start, end);
int remainingLength = 0;
unsigned next = 0;
bool needMoreContext = false;
while (!it.atEnd()) {
bool inTextSecurityMode = it.isInTextSecurityMode();
// iterate to get chunks until the searchFunction returns a non-zero
// value.
if (!inTextSecurityMode) {
int runOffset = 0;
do {
runOffset += it.copyTextTo(&string, runOffset, string.capacity());
// TODO(xiaochengh): The following line takes O(string.size()) time,
// which makes quadratic overall running time in the worst case.
// Should improve it in some way.
next = searchFunction(string.data(), string.size(),
string.size() - suffixLength, MayHaveMoreContext,
needMoreContext);
} while (!next && runOffset < it.length());
if (next) {
remainingLength = it.length() - runOffset;
break;
}
} else {
// Treat bullets used in the text security mode as regular
// characters when looking for boundaries
string.pushCharacters('x', it.length());
next = 0;
}
it.advance();
}
if (needMoreContext) {
// The last search returned the beginning of the buffer and asked for
// more context, but there is no earlier text. Force a search with
// what's available.
// TODO(xiaochengh): Do we have to search the whole string?
next = searchFunction(string.data(), string.size(),
string.size() - suffixLength, DontHaveMoreContext,
needMoreContext);
DCHECK(!needMoreContext);
}
if (!next)
return createVisiblePosition(it.atEnd() ? it.startPosition() : pos);
Node* node = it.startContainer();
int boundaryOffset = remainingLength + next;
if (node->isTextNode() && boundaryOffset <= node->maxCharacterOffset()) {
// The next variable contains a usable index into a text node
return createVisiblePosition(
PositionTemplate<Strategy>(node, boundaryOffset));
}
// Use the character iterator to translate the next value into a DOM
// position.
BackwardsCharacterIteratorAlgorithm<Strategy> charIt(start, end);
charIt.advance(string.size() - suffixLength - next);
// TODO(yosin) charIt can get out of shadow host.
return createVisiblePosition(charIt.endPosition());
}
template <typename Strategy>
static VisiblePositionTemplate<Strategy> nextBoundary(
const VisiblePositionTemplate<Strategy>& c,
BoundarySearchFunction searchFunction) {
DCHECK(c.isValid()) << c;
PositionTemplate<Strategy> pos = c.deepEquivalent();
Node* boundary = parentEditingBoundary(pos);
if (!boundary)
return VisiblePositionTemplate<Strategy>();
Document& d = boundary->document();
const PositionTemplate<Strategy> start(pos.parentAnchoredEquivalent());
BackwardsTextBuffer prefixString;
if (requiresContextForWordBoundary(characterAfter(c))) {
SimplifiedBackwardsTextIteratorAlgorithm<Strategy> backwardsIterator(
PositionTemplate<Strategy>::firstPositionInNode(&d), start);
while (!backwardsIterator.atEnd()) {
backwardsIterator.copyTextTo(&prefixString);
int contextStartIndex = startOfLastWordBoundaryContext(
prefixString.data(), backwardsIterator.length());
if (contextStartIndex > 0) {
prefixString.shrink(contextStartIndex);
break;
}
backwardsIterator.advance();
}
}
unsigned prefixLength = prefixString.size();
ForwardsTextBuffer string;
string.pushRange(prefixString.data(), prefixString.size());
const PositionTemplate<Strategy> searchStart =
PositionTemplate<Strategy>::editingPositionOf(
start.anchorNode(), start.offsetInContainerNode());
const PositionTemplate<Strategy> searchEnd =
PositionTemplate<Strategy>::lastPositionInNode(boundary);
TextIteratorAlgorithm<Strategy> it(
searchStart, searchEnd,
TextIteratorEmitsCharactersBetweenAllVisiblePositions);
const unsigned invalidOffset = static_cast<unsigned>(-1);
unsigned next = invalidOffset;
unsigned offset = prefixLength;
bool needMoreContext = false;
while (!it.atEnd()) {
// Keep asking the iterator for chunks until the search function
// returns an end value not equal to the length of the string passed to
// it.
bool inTextSecurityMode = it.isInTextSecurityMode();
if (!inTextSecurityMode) {
int runOffset = 0;
do {
runOffset += it.copyTextTo(&string, runOffset, string.capacity());
next = searchFunction(string.data(), string.size(), offset,
MayHaveMoreContext, needMoreContext);
if (!needMoreContext) {
// When the search does not need more context, skip all examined
// characters except the last one, in case it is a boundary.
offset = string.size();
U16_BACK_1(string.data(), 0, offset);
}
} while (next == string.size() && runOffset < it.length());
if (next != string.size())
break;
} else {
// Treat bullets used in the text security mode as regular
// characters when looking for boundaries
string.pushCharacters('x', it.length());
next = string.size();
}
it.advance();
}
if (needMoreContext) {
// The last search returned the end of the buffer and asked for more
// context, but there is no further text. Force a search with what's
// available.
// TODO(xiaochengh): Do we still have to search the whole string?
next = searchFunction(string.data(), string.size(), prefixLength,
DontHaveMoreContext, needMoreContext);
DCHECK(!needMoreContext);
}
if (it.atEnd() && next == string.size()) {
pos = it.startPositionInCurrentContainer();
} else if (next != invalidOffset && next != prefixLength) {
// Use the character iterator to translate the next value into a DOM
// position.
CharacterIteratorAlgorithm<Strategy> charIt(
searchStart, searchEnd,
TextIteratorEmitsCharactersBetweenAllVisiblePositions);
charIt.advance(next - prefixLength - 1);
pos = charIt.endPosition();
if (charIt.characterAt(0) == '\n') {
// TODO(yosin) workaround for collapsed range (where only start
// position is correct) emitted for some emitted newlines
// (see rdar://5192593)
const VisiblePositionTemplate<Strategy> visPos =
createVisiblePosition(pos);
if (visPos.deepEquivalent() ==
createVisiblePosition(charIt.startPosition()).deepEquivalent()) {
charIt.advance(1);
pos = charIt.startPosition();
}
}
}
// generate VisiblePosition, use TextAffinity::Upstream affinity if possible
return createVisiblePosition(pos, VP_UPSTREAM_IF_POSSIBLE);
}
// ---------
static unsigned startWordBoundary(
const UChar* characters,
unsigned length,
unsigned offset,
BoundarySearchContextAvailability mayHaveMoreContext,
bool& needMoreContext) {
TRACE_EVENT0("blink", "startWordBoundary");
DCHECK(offset);
if (mayHaveMoreContext &&
!startOfLastWordBoundaryContext(characters, offset)) {
needMoreContext = true;
return 0;
}
needMoreContext = false;
int start, end;
U16_BACK_1(characters, 0, offset);
findWordBoundary(characters, length, offset, &start, &end);
return start;
}
template <typename Strategy>
static VisiblePositionTemplate<Strategy> startOfWordAlgorithm(
const VisiblePositionTemplate<Strategy>& c,
EWordSide side) {
DCHECK(c.isValid()) << c;
// TODO(yosin) This returns a null VP for c at the start of the document
// and |side| == |LeftWordIfOnBoundary|
VisiblePositionTemplate<Strategy> p = c;
if (side == RightWordIfOnBoundary) {
// at paragraph end, the startofWord is the current position
if (isEndOfParagraph(c))
return c;
p = nextPositionOf(c);
if (p.isNull())
return c;
}
return previousBoundary(p, startWordBoundary);
}
VisiblePosition startOfWord(const VisiblePosition& c, EWordSide side) {
return startOfWordAlgorithm<EditingStrategy>(c, side);
}
VisiblePositionInFlatTree startOfWord(const VisiblePositionInFlatTree& c,
EWordSide side) {
return startOfWordAlgorithm<EditingInFlatTreeStrategy>(c, side);
}
static unsigned endWordBoundary(
const UChar* characters,
unsigned length,
unsigned offset,
BoundarySearchContextAvailability mayHaveMoreContext,
bool& needMoreContext) {
DCHECK_LE(offset, length);
if (mayHaveMoreContext &&
endOfFirstWordBoundaryContext(characters + offset, length - offset) ==
static_cast<int>(length - offset)) {
needMoreContext = true;
return length;
}
needMoreContext = false;
return findWordEndBoundary(characters, length, offset);
}
template <typename Strategy>
static VisiblePositionTemplate<Strategy> endOfWordAlgorithm(
const VisiblePositionTemplate<Strategy>& c,
EWordSide side) {
DCHECK(c.isValid()) << c;
VisiblePositionTemplate<Strategy> p = c;
if (side == LeftWordIfOnBoundary) {
if (isStartOfParagraph(c))
return c;
p = previousPositionOf(c);
if (p.isNull())
return c;
} else if (isEndOfParagraph(c)) {
return c;
}
return nextBoundary(p, endWordBoundary);
}
VisiblePosition endOfWord(const VisiblePosition& c, EWordSide side) {
return endOfWordAlgorithm<EditingStrategy>(c, side);
}
VisiblePositionInFlatTree endOfWord(const VisiblePositionInFlatTree& c,
EWordSide side) {
return endOfWordAlgorithm<EditingInFlatTreeStrategy>(c, side);
}
static unsigned previousWordPositionBoundary(
const UChar* characters,
unsigned length,
unsigned offset,
BoundarySearchContextAvailability mayHaveMoreContext,
bool& needMoreContext) {
if (mayHaveMoreContext &&
!startOfLastWordBoundaryContext(characters, offset)) {
needMoreContext = true;
return 0;
}
needMoreContext = false;
return findNextWordFromIndex(characters, length, offset, false);
}
VisiblePosition previousWordPosition(const VisiblePosition& c) {
DCHECK(c.isValid()) << c;
VisiblePosition prev = previousBoundary(c, previousWordPositionBoundary);
return honorEditingBoundaryAtOrBefore(prev, c.deepEquivalent());
}
static unsigned nextWordPositionBoundary(
const UChar* characters,
unsigned length,
unsigned offset,
BoundarySearchContextAvailability mayHaveMoreContext,
bool& needMoreContext) {
if (mayHaveMoreContext &&
endOfFirstWordBoundaryContext(characters + offset, length - offset) ==
static_cast<int>(length - offset)) {
needMoreContext = true;
return length;
}
needMoreContext = false;
return findNextWordFromIndex(characters, length, offset, true);
}
VisiblePosition nextWordPosition(const VisiblePosition& c) {
DCHECK(c.isValid()) << c;
VisiblePosition next = nextBoundary(c, nextWordPositionBoundary);
return honorEditingBoundaryAtOrAfter(next, c.deepEquivalent());
}
// ---------
enum LineEndpointComputationMode { UseLogicalOrdering, UseInlineBoxOrdering };
template <typename Strategy>
static PositionWithAffinityTemplate<Strategy> startPositionForLine(
const PositionWithAffinityTemplate<Strategy>& c,
LineEndpointComputationMode mode) {
if (c.isNull())
return PositionWithAffinityTemplate<Strategy>();
RootInlineBox* rootBox =
RenderedPosition(c.position(), c.affinity()).rootBox();
if (!rootBox) {
// There are VisiblePositions at offset 0 in blocks without
// RootInlineBoxes, like empty editable blocks and bordered blocks.
PositionTemplate<Strategy> p = c.position();
if (p.anchorNode()->layoutObject() &&
p.anchorNode()->layoutObject()->isLayoutBlock() &&
!p.computeEditingOffset())
return c;
return PositionWithAffinityTemplate<Strategy>();
}
Node* startNode;
InlineBox* startBox;
if (mode == UseLogicalOrdering) {
startNode = rootBox->getLogicalStartBoxWithNode(startBox);
if (!startNode)
return PositionWithAffinityTemplate<Strategy>();
} else {
// Generated content (e.g. list markers and CSS :before and :after
// pseudoelements) have no corresponding DOM element, and so cannot be
// represented by a VisiblePosition. Use whatever follows instead.
startBox = rootBox->firstLeafChild();
while (true) {
if (!startBox)
return PositionWithAffinityTemplate<Strategy>();
startNode = startBox->getLineLayoutItem().nonPseudoNode();
if (startNode)
break;
startBox = startBox->nextLeafChild();
}
}
return PositionWithAffinityTemplate<Strategy>(
startNode->isTextNode()
? PositionTemplate<Strategy>(toText(startNode),
toInlineTextBox(startBox)->start())
: PositionTemplate<Strategy>::beforeNode(startNode));
}
template <typename Strategy>
static PositionWithAffinityTemplate<Strategy> startOfLineAlgorithm(
const PositionWithAffinityTemplate<Strategy>& c) {
// TODO: this is the current behavior that might need to be fixed.
// Please refer to https://bugs.webkit.org/show_bug.cgi?id=49107 for detail.
PositionWithAffinityTemplate<Strategy> visPos =
startPositionForLine(c, UseInlineBoxOrdering);
return honorEditingBoundaryAtOrBefore(visPos, c.position());
}
static PositionWithAffinity startOfLine(
const PositionWithAffinity& currentPosition) {
return startOfLineAlgorithm<EditingStrategy>(currentPosition);
}
static PositionInFlatTreeWithAffinity startOfLine(
const PositionInFlatTreeWithAffinity& currentPosition) {
return startOfLineAlgorithm<EditingInFlatTreeStrategy>(currentPosition);
}
// FIXME: Rename this function to reflect the fact it ignores bidi levels.
VisiblePosition startOfLine(const VisiblePosition& currentPosition) {
DCHECK(currentPosition.isValid()) << currentPosition;
return createVisiblePosition(
startOfLine(currentPosition.toPositionWithAffinity()));
}
VisiblePositionInFlatTree startOfLine(
const VisiblePositionInFlatTree& currentPosition) {
DCHECK(currentPosition.isValid()) << currentPosition;
return createVisiblePosition(
startOfLine(currentPosition.toPositionWithAffinity()));
}
template <typename Strategy>
static PositionWithAffinityTemplate<Strategy> logicalStartOfLineAlgorithm(
const PositionWithAffinityTemplate<Strategy>& c) {
// TODO: this is the current behavior that might need to be fixed.
// Please refer to https://bugs.webkit.org/show_bug.cgi?id=49107 for detail.
PositionWithAffinityTemplate<Strategy> visPos =
startPositionForLine(c, UseLogicalOrdering);
if (ContainerNode* editableRoot = highestEditableRoot(c.position())) {
if (!editableRoot->contains(visPos.position().computeContainerNode()))
return PositionWithAffinityTemplate<Strategy>(
PositionTemplate<Strategy>::firstPositionInNode(editableRoot));
}
return honorEditingBoundaryAtOrBefore(visPos, c.position());
}
VisiblePosition logicalStartOfLine(const VisiblePosition& currentPosition) {
DCHECK(currentPosition.isValid()) << currentPosition;
return createVisiblePosition(logicalStartOfLineAlgorithm<EditingStrategy>(
currentPosition.toPositionWithAffinity()));
}
VisiblePositionInFlatTree logicalStartOfLine(
const VisiblePositionInFlatTree& currentPosition) {
DCHECK(currentPosition.isValid()) << currentPosition;
return createVisiblePosition(
logicalStartOfLineAlgorithm<EditingInFlatTreeStrategy>(
currentPosition.toPositionWithAffinity()));
}
template <typename Strategy>
static VisiblePositionTemplate<Strategy> endPositionForLine(
const VisiblePositionTemplate<Strategy>& c,
LineEndpointComputationMode mode) {
DCHECK(c.isValid()) << c;
if (c.isNull())
return VisiblePositionTemplate<Strategy>();
RootInlineBox* rootBox = RenderedPosition(c).rootBox();
if (!rootBox) {
// There are VisiblePositions at offset 0 in blocks without
// RootInlineBoxes, like empty editable blocks and bordered blocks.
const PositionTemplate<Strategy> p = c.deepEquivalent();
if (p.anchorNode()->layoutObject() &&
p.anchorNode()->layoutObject()->isLayoutBlock() &&
!p.computeEditingOffset())
return c;
return VisiblePositionTemplate<Strategy>();
}
Node* endNode;
InlineBox* endBox;
if (mode == UseLogicalOrdering) {
endNode = rootBox->getLogicalEndBoxWithNode(endBox);
if (!endNode)
return VisiblePositionTemplate<Strategy>();
} else {
// Generated content (e.g. list markers and CSS :before and :after
// pseudo elements) have no corresponding DOM element, and so cannot be
// represented by a VisiblePosition. Use whatever precedes instead.
endBox = rootBox->lastLeafChild();
while (true) {
if (!endBox)
return VisiblePositionTemplate<Strategy>();
endNode = endBox->getLineLayoutItem().nonPseudoNode();
if (endNode)
break;
endBox = endBox->prevLeafChild();
}
}
PositionTemplate<Strategy> pos;
if (isHTMLBRElement(*endNode)) {
pos = PositionTemplate<Strategy>::beforeNode(endNode);
} else if (endBox->isInlineTextBox() && endNode->isTextNode()) {
InlineTextBox* endTextBox = toInlineTextBox(endBox);
int endOffset = endTextBox->start();
if (!endTextBox->isLineBreak())
endOffset += endTextBox->len();
pos = PositionTemplate<Strategy>(toText(endNode), endOffset);
} else {
pos = PositionTemplate<Strategy>::afterNode(endNode);
}
return createVisiblePosition(pos, VP_UPSTREAM_IF_POSSIBLE);
}
// TODO(yosin) Rename this function to reflect the fact it ignores bidi levels.
template <typename Strategy>
static VisiblePositionTemplate<Strategy> endOfLineAlgorithm(
const VisiblePositionTemplate<Strategy>& currentPosition) {
DCHECK(currentPosition.isValid()) << currentPosition;
// TODO(yosin) this is the current behavior that might need to be fixed.
// Please refer to https://bugs.webkit.org/show_bug.cgi?id=49107 for detail.
VisiblePositionTemplate<Strategy> visPos =
endPositionForLine(currentPosition, UseInlineBoxOrdering);
// Make sure the end of line is at the same line as the given input
// position. Else use the previous position to obtain end of line. This
// condition happens when the input position is before the space character
// at the end of a soft-wrapped non-editable line. In this scenario,
// |endPositionForLine()| would incorrectly hand back a position in the next
// line instead. This fix is to account for the discrepancy between lines
// with "webkit-line-break:after-white-space" style versus lines without
// that style, which would break before a space by default.
if (!inSameLine(currentPosition, visPos)) {
visPos = previousPositionOf(currentPosition);
if (visPos.isNull())
return VisiblePositionTemplate<Strategy>();
visPos = endPositionForLine(visPos, UseInlineBoxOrdering);
}
return honorEditingBoundaryAtOrAfter(visPos,
currentPosition.deepEquivalent());
}
// TODO(yosin) Rename this function to reflect the fact it ignores bidi levels.
VisiblePosition endOfLine(const VisiblePosition& currentPosition) {
return endOfLineAlgorithm<EditingStrategy>(currentPosition);
}
VisiblePositionInFlatTree endOfLine(
const VisiblePositionInFlatTree& currentPosition) {
return endOfLineAlgorithm<EditingInFlatTreeStrategy>(currentPosition);
}
template <typename Strategy>
static bool inSameLogicalLine(const VisiblePositionTemplate<Strategy>& a,
const VisiblePositionTemplate<Strategy>& b) {
DCHECK(a.isValid()) << a;
DCHECK(b.isValid()) << b;
return a.isNotNull() &&
logicalStartOfLine(a).deepEquivalent() ==
logicalStartOfLine(b).deepEquivalent();
}
template <typename Strategy>
VisiblePositionTemplate<Strategy> logicalEndOfLineAlgorithm(
const VisiblePositionTemplate<Strategy>& currentPosition) {
DCHECK(currentPosition.isValid()) << currentPosition;
// TODO(yosin) this is the current behavior that might need to be fixed.
// Please refer to https://bugs.webkit.org/show_bug.cgi?id=49107 for detail.
VisiblePositionTemplate<Strategy> visPos =
endPositionForLine(currentPosition, UseLogicalOrdering);
// Make sure the end of line is at the same line as the given input
// position. For a wrapping line, the logical end position for the
// not-last-2-lines might incorrectly hand back the logical beginning of the
// next line. For example,
// <div contenteditable dir="rtl" style="line-break:before-white-space">xyz
// a xyz xyz xyz xyz xyz xyz xyz xyz xyz xyz </div>
// In this case, use the previous position of the computed logical end
// position.
if (!inSameLogicalLine(currentPosition, visPos))
visPos = previousPositionOf(visPos);
if (ContainerNode* editableRoot =
highestEditableRoot(currentPosition.deepEquivalent())) {
if (!editableRoot->contains(visPos.deepEquivalent().computeContainerNode()))
return createVisiblePosition(
PositionTemplate<Strategy>::lastPositionInNode(editableRoot));
}
return honorEditingBoundaryAtOrAfter(visPos,
currentPosition.deepEquivalent());
}
VisiblePosition logicalEndOfLine(const VisiblePosition& currentPosition) {
return logicalEndOfLineAlgorithm<EditingStrategy>(currentPosition);
}
VisiblePositionInFlatTree logicalEndOfLine(
const VisiblePositionInFlatTree& currentPosition) {
return logicalEndOfLineAlgorithm<EditingInFlatTreeStrategy>(currentPosition);
}
template <typename Strategy>
bool inSameLineAlgorithm(
const PositionWithAffinityTemplate<Strategy>& position1,
const PositionWithAffinityTemplate<Strategy>& position2) {
if (position1.isNull() || position2.isNull())
return false;
DCHECK_EQ(position1.document(), position2.document());
DCHECK(!position1.document()->needsLayoutTreeUpdate());
PositionWithAffinityTemplate<Strategy> startOfLine1 = startOfLine(position1);
PositionWithAffinityTemplate<Strategy> startOfLine2 = startOfLine(position2);
if (startOfLine1 == startOfLine2)
return true;
PositionTemplate<Strategy> canonicalized1 =
canonicalPositionOf(startOfLine1.position());
if (canonicalized1 == startOfLine2.position())
return true;
return canonicalized1 == canonicalPositionOf(startOfLine2.position());
}
bool inSameLine(const PositionWithAffinity& a, const PositionWithAffinity& b) {
return inSameLineAlgorithm<EditingStrategy>(a, b);
}
bool inSameLine(const PositionInFlatTreeWithAffinity& position1,
const PositionInFlatTreeWithAffinity& position2) {
return inSameLineAlgorithm<EditingInFlatTreeStrategy>(position1, position2);
}
bool inSameLine(const VisiblePosition& position1,
const VisiblePosition& position2) {
DCHECK(position1.isValid()) << position1;
DCHECK(position2.isValid()) << position2;
return inSameLine(position1.toPositionWithAffinity(),
position2.toPositionWithAffinity());
}
bool inSameLine(const VisiblePositionInFlatTree& position1,
const VisiblePositionInFlatTree& position2) {
DCHECK(position1.isValid()) << position1;
DCHECK(position2.isValid()) << position2;
return inSameLine(position1.toPositionWithAffinity(),
position2.toPositionWithAffinity());
}
template <typename Strategy>
bool isStartOfLineAlgorithm(const VisiblePositionTemplate<Strategy>& p) {
DCHECK(p.isValid()) << p;
return p.isNotNull() && p.deepEquivalent() == startOfLine(p).deepEquivalent();
}
bool isStartOfLine(const VisiblePosition& p) {
return isStartOfLineAlgorithm<EditingStrategy>(p);
}
bool isStartOfLine(const VisiblePositionInFlatTree& p) {
return isStartOfLineAlgorithm<EditingInFlatTreeStrategy>(p);
}
template <typename Strategy>
bool isEndOfLineAlgorithm(const VisiblePositionTemplate<Strategy>& p) {
DCHECK(p.isValid()) << p;
return p.isNotNull() && p.deepEquivalent() == endOfLine(p).deepEquivalent();
}
bool isEndOfLine(const VisiblePosition& p) {
return isEndOfLineAlgorithm<EditingStrategy>(p);
}
bool isEndOfLine(const VisiblePositionInFlatTree& p) {
return isEndOfLineAlgorithm<EditingInFlatTreeStrategy>(p);
}
template <typename Strategy>
static bool isLogicalEndOfLineAlgorithm(
const VisiblePositionTemplate<Strategy>& p) {
DCHECK(p.isValid()) << p;
return p.isNotNull() &&
p.deepEquivalent() == logicalEndOfLine(p).deepEquivalent();
}
bool isLogicalEndOfLine(const VisiblePosition& p) {
return isLogicalEndOfLineAlgorithm<EditingStrategy>(p);
}
bool isLogicalEndOfLine(const VisiblePositionInFlatTree& p) {
return isLogicalEndOfLineAlgorithm<EditingInFlatTreeStrategy>(p);
}
static inline LayoutPoint absoluteLineDirectionPointToLocalPointInBlock(
RootInlineBox* root,
LayoutUnit lineDirectionPoint) {
DCHECK(root);
LineLayoutBlockFlow containingBlock = root->block();
FloatPoint absoluteBlockPoint = containingBlock.localToAbsolute(FloatPoint());
if (containingBlock.hasOverflowClip())
absoluteBlockPoint -= FloatSize(containingBlock.scrolledContentOffset());
if (root->block().isHorizontalWritingMode())
return LayoutPoint(LayoutUnit(lineDirectionPoint - absoluteBlockPoint.x()),
root->blockDirectionPointInLine());
return LayoutPoint(root->blockDirectionPointInLine(),
LayoutUnit(lineDirectionPoint - absoluteBlockPoint.y()));
}
VisiblePosition previousLinePosition(const VisiblePosition& visiblePosition,
LayoutUnit lineDirectionPoint,
EditableType editableType) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
Position p = visiblePosition.deepEquivalent();
Node* node = p.anchorNode();
if (!node)
return VisiblePosition();
LayoutObject* layoutObject = node->layoutObject();
if (!layoutObject)
return VisiblePosition();
RootInlineBox* root = 0;
InlineBox* box = computeInlineBoxPosition(visiblePosition).inlineBox;
if (box) {
root = box->root().prevRootBox();
// We want to skip zero height boxes.
// This could happen in case it is a TrailingFloatsRootInlineBox.
if (!root || !root->logicalHeight() || !root->firstLeafChild())
root = 0;
}
if (!root) {
Position position = previousRootInlineBoxCandidatePosition(
node, visiblePosition, editableType);
if (position.isNotNull()) {
RenderedPosition renderedPosition((createVisiblePosition(position)));
root = renderedPosition.rootBox();
if (!root)
return createVisiblePosition(position);
}
}
if (root) {
// FIXME: Can be wrong for multi-column layout and with transforms.
LayoutPoint pointInLine =
absoluteLineDirectionPointToLocalPointInBlock(root, lineDirectionPoint);
LineLayoutItem lineLayoutItem =
root->closestLeafChildForPoint(pointInLine, isEditablePosition(p))
->getLineLayoutItem();
Node* node = lineLayoutItem.node();
if (node && editingIgnoresContent(*node))
return VisiblePosition::inParentBeforeNode(*node);
return createVisiblePosition(lineLayoutItem.positionForPoint(pointInLine));
}
// Could not find a previous line. This means we must already be on the first
// line. Move to the start of the content in this block, which effectively
// moves us to the start of the line we're on.
Element* rootElement = hasEditableStyle(*node, editableType)
? rootEditableElement(*node, editableType)
: node->document().documentElement();
if (!rootElement)
return VisiblePosition();
return VisiblePosition::firstPositionInNode(rootElement);
}
VisiblePosition nextLinePosition(const VisiblePosition& visiblePosition,
LayoutUnit lineDirectionPoint,
EditableType editableType) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
Position p = visiblePosition.deepEquivalent();
Node* node = p.anchorNode();
if (!node)
return VisiblePosition();
LayoutObject* layoutObject = node->layoutObject();
if (!layoutObject)
return VisiblePosition();
RootInlineBox* root = 0;
InlineBox* box = computeInlineBoxPosition(visiblePosition).inlineBox;
if (box) {
root = box->root().nextRootBox();
// We want to skip zero height boxes.
// This could happen in case it is a TrailingFloatsRootInlineBox.
if (!root || !root->logicalHeight() || !root->firstLeafChild())
root = 0;
}
if (!root) {
// FIXME: We need do the same in previousLinePosition.
Node* child = NodeTraversal::childAt(*node, p.computeEditingOffset());
node = child ? child : &NodeTraversal::lastWithinOrSelf(*node);
Position position =
nextRootInlineBoxCandidatePosition(node, visiblePosition, editableType);
if (position.isNotNull()) {
RenderedPosition renderedPosition((createVisiblePosition(position)));
root = renderedPosition.rootBox();
if (!root)
return createVisiblePosition(position);
}
}
if (root) {
// FIXME: Can be wrong for multi-column layout and with transforms.
LayoutPoint pointInLine =
absoluteLineDirectionPointToLocalPointInBlock(root, lineDirectionPoint);
LineLayoutItem lineLayoutItem =
root->closestLeafChildForPoint(pointInLine, isEditablePosition(p))
->getLineLayoutItem();
Node* node = lineLayoutItem.node();
if (node && editingIgnoresContent(*node))
return VisiblePosition::inParentBeforeNode(*node);
return createVisiblePosition(lineLayoutItem.positionForPoint(pointInLine));
}
// Could not find a next line. This means we must already be on the last line.
// Move to the end of the content in this block, which effectively moves us
// to the end of the line we're on.
Element* rootElement = hasEditableStyle(*node, editableType)
? rootEditableElement(*node, editableType)
: node->document().documentElement();
if (!rootElement)
return VisiblePosition();
return VisiblePosition::lastPositionInNode(rootElement);
}
// ---------
static unsigned startSentenceBoundary(const UChar* characters,
unsigned length,
unsigned,
BoundarySearchContextAvailability,
bool&) {
TextBreakIterator* iterator = sentenceBreakIterator(characters, length);
// FIXME: The following function can return -1; we don't handle that.
return iterator->preceding(length);
}
template <typename Strategy>
static VisiblePositionTemplate<Strategy> startOfSentenceAlgorithm(
const VisiblePositionTemplate<Strategy>& c) {
DCHECK(c.isValid()) << c;
return previousBoundary(c, startSentenceBoundary);
}
VisiblePosition startOfSentence(const VisiblePosition& c) {
return startOfSentenceAlgorithm<EditingStrategy>(c);
}
VisiblePositionInFlatTree startOfSentence(const VisiblePositionInFlatTree& c) {
return startOfSentenceAlgorithm<EditingInFlatTreeStrategy>(c);
}
static unsigned endSentenceBoundary(const UChar* characters,
unsigned length,
unsigned,
BoundarySearchContextAvailability,
bool&) {
TextBreakIterator* iterator = sentenceBreakIterator(characters, length);
return iterator->next();
}
// TODO(yosin) This includes the space after the punctuation that marks the end
// of the sentence.
template <typename Strategy>
static VisiblePositionTemplate<Strategy> endOfSentenceAlgorithm(
const VisiblePositionTemplate<Strategy>& c) {
DCHECK(c.isValid()) << c;
return nextBoundary(c, endSentenceBoundary);
}
VisiblePosition endOfSentence(const VisiblePosition& c) {
return endOfSentenceAlgorithm<EditingStrategy>(c);
}
VisiblePositionInFlatTree endOfSentence(const VisiblePositionInFlatTree& c) {
return endOfSentenceAlgorithm<EditingInFlatTreeStrategy>(c);
}
static unsigned previousSentencePositionBoundary(
const UChar* characters,
unsigned length,
unsigned,
BoundarySearchContextAvailability,
bool&) {
// FIXME: This is identical to startSentenceBoundary. I'm pretty sure that's
// not right.
TextBreakIterator* iterator = sentenceBreakIterator(characters, length);
// FIXME: The following function can return -1; we don't handle that.
return iterator->preceding(length);
}
VisiblePosition previousSentencePosition(const VisiblePosition& c) {
DCHECK(c.isValid()) << c;
VisiblePosition prev = previousBoundary(c, previousSentencePositionBoundary);
return honorEditingBoundaryAtOrBefore(prev, c.deepEquivalent());
}
static unsigned nextSentencePositionBoundary(const UChar* characters,
unsigned length,
unsigned,
BoundarySearchContextAvailability,
bool&) {
// FIXME: This is identical to endSentenceBoundary. This isn't right, it needs
// to move to the equivlant position in the following sentence.
TextBreakIterator* iterator = sentenceBreakIterator(characters, length);
return iterator->following(0);
}
VisiblePosition nextSentencePosition(const VisiblePosition& c) {
DCHECK(c.isValid()) << c;
VisiblePosition next = nextBoundary(c, nextSentencePositionBoundary);
return honorEditingBoundaryAtOrAfter(next, c.deepEquivalent());
}
template <typename Strategy>
PositionTemplate<Strategy> startOfParagraphAlgorithm(
const PositionTemplate<Strategy>& position,
EditingBoundaryCrossingRule boundaryCrossingRule) {
Node* const startNode = position.anchorNode();
if (!startNode)
return PositionTemplate<Strategy>();
if (isRenderedAsNonInlineTableImageOrHR(startNode))
return PositionTemplate<Strategy>::beforeNode(startNode);
Element* const startBlock = enclosingBlock(
PositionTemplate<Strategy>::firstPositionInOrBeforeNode(startNode),
CannotCrossEditingBoundary);
ContainerNode* const highestRoot = highestEditableRoot(position);
const bool startNodeIsEditable = hasEditableStyle(*startNode);
Node* candidateNode = startNode;
PositionAnchorType candidateType = position.anchorType();
int candidateOffset = position.computeEditingOffset();
Node* prevousNodeIterator = startNode;
while (prevousNodeIterator) {
if (boundaryCrossingRule == CannotCrossEditingBoundary &&
!nodeIsUserSelectAll(prevousNodeIterator) &&
hasEditableStyle(*prevousNodeIterator) != startNodeIsEditable)
break;
if (boundaryCrossingRule == CanSkipOverEditingBoundary) {
while (prevousNodeIterator &&
hasEditableStyle(*prevousNodeIterator) != startNodeIsEditable)
prevousNodeIterator =
Strategy::previousPostOrder(*prevousNodeIterator, startBlock);
if (!prevousNodeIterator ||
!prevousNodeIterator->isDescendantOf(highestRoot))
break;
}
const LayoutItem layoutItem =
LayoutItem(prevousNodeIterator->layoutObject());
if (layoutItem.isNull()) {
prevousNodeIterator =
Strategy::previousPostOrder(*prevousNodeIterator, startBlock);
continue;
}
const ComputedStyle& style = layoutItem.styleRef();
if (style.visibility() != EVisibility::Visible) {
prevousNodeIterator =
Strategy::previousPostOrder(*prevousNodeIterator, startBlock);
continue;
}
if (layoutItem.isBR() || isEnclosingBlock(prevousNodeIterator))
break;
if (layoutItem.isText() &&
toLayoutText(prevousNodeIterator->layoutObject())
->resolvedTextLength()) {
SECURITY_DCHECK(prevousNodeIterator->isTextNode());
if (style.preserveNewline()) {
LayoutText* text = toLayoutText(prevousNodeIterator->layoutObject());
int index = text->textLength();
if (prevousNodeIterator == startNode && candidateOffset < index)
index = max(0, candidateOffset);
while (--index >= 0) {
if ((*text)[index] == '\n')
return PositionTemplate<Strategy>(toText(prevousNodeIterator),
index + 1);
}
}
candidateNode = prevousNodeIterator;
candidateType = PositionAnchorType::OffsetInAnchor;
candidateOffset = 0;
prevousNodeIterator =
Strategy::previousPostOrder(*prevousNodeIterator, startBlock);
} else if (editingIgnoresContent(*prevousNodeIterator) ||
isDisplayInsideTable(prevousNodeIterator)) {
candidateNode = prevousNodeIterator;
candidateType = PositionAnchorType::BeforeAnchor;
prevousNodeIterator =
prevousNodeIterator->previousSibling()
? prevousNodeIterator->previousSibling()
: Strategy::previousPostOrder(*prevousNodeIterator, startBlock);
} else {
prevousNodeIterator =
Strategy::previousPostOrder(*prevousNodeIterator, startBlock);
}
}
if (candidateType == PositionAnchorType::OffsetInAnchor)
return PositionTemplate<Strategy>(candidateNode, candidateOffset);
return PositionTemplate<Strategy>(candidateNode, candidateType);
}
template <typename Strategy>
VisiblePositionTemplate<Strategy> startOfParagraphAlgorithm(
const VisiblePositionTemplate<Strategy>& visiblePosition,
EditingBoundaryCrossingRule boundaryCrossingRule) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
return createVisiblePosition(startOfParagraphAlgorithm(
visiblePosition.deepEquivalent(), boundaryCrossingRule));
}
VisiblePosition startOfParagraph(
const VisiblePosition& c,
EditingBoundaryCrossingRule boundaryCrossingRule) {
return startOfParagraphAlgorithm<EditingStrategy>(c, boundaryCrossingRule);
}
VisiblePositionInFlatTree startOfParagraph(
const VisiblePositionInFlatTree& c,
EditingBoundaryCrossingRule boundaryCrossingRule) {
return startOfParagraphAlgorithm<EditingInFlatTreeStrategy>(
c, boundaryCrossingRule);
}
template <typename Strategy>
static PositionTemplate<Strategy> endOfParagraphAlgorithm(
const PositionTemplate<Strategy>& position,
EditingBoundaryCrossingRule boundaryCrossingRule) {
Node* const startNode = position.anchorNode();
if (!startNode)
return PositionTemplate<Strategy>();
if (isRenderedAsNonInlineTableImageOrHR(startNode))
return PositionTemplate<Strategy>::afterNode(startNode);
Element* const startBlock = enclosingBlock(
PositionTemplate<Strategy>::firstPositionInOrBeforeNode(startNode),
CannotCrossEditingBoundary);
ContainerNode* const highestRoot = highestEditableRoot(position);
const bool startNodeIsEditable = hasEditableStyle(*startNode);
Node* candidateNode = startNode;
PositionAnchorType candidateType = position.anchorType();
int candidateOffset = position.computeEditingOffset();
Node* nextNodeItreator = startNode;
while (nextNodeItreator) {
if (boundaryCrossingRule == CannotCrossEditingBoundary &&
!nodeIsUserSelectAll(nextNodeItreator) &&
hasEditableStyle(*nextNodeItreator) != startNodeIsEditable)
break;
if (boundaryCrossingRule == CanSkipOverEditingBoundary) {
while (nextNodeItreator &&
hasEditableStyle(*nextNodeItreator) != startNodeIsEditable)
nextNodeItreator = Strategy::next(*nextNodeItreator, startBlock);
if (!nextNodeItreator || !nextNodeItreator->isDescendantOf(highestRoot))
break;
}
LayoutObject* const layoutObject = nextNodeItreator->layoutObject();
if (!layoutObject) {
nextNodeItreator = Strategy::next(*nextNodeItreator, startBlock);
continue;
}
const ComputedStyle& style = layoutObject->styleRef();
if (style.visibility() != EVisibility::Visible) {
nextNodeItreator = Strategy::next(*nextNodeItreator, startBlock);
continue;
}
if (layoutObject->isBR() || isEnclosingBlock(nextNodeItreator))
break;
// FIXME: We avoid returning a position where the layoutObject can't accept
// the caret.
if (layoutObject->isText() &&
toLayoutText(layoutObject)->resolvedTextLength()) {
SECURITY_DCHECK(nextNodeItreator->isTextNode());
LayoutText* const text = toLayoutText(layoutObject);
if (style.preserveNewline()) {
const int length = toLayoutText(layoutObject)->textLength();
for (int i = (nextNodeItreator == startNode ? candidateOffset : 0);
i < length; ++i) {
if ((*text)[i] == '\n')
return PositionTemplate<Strategy>(toText(nextNodeItreator),
i + text->textStartOffset());
}
}
candidateNode = nextNodeItreator;
candidateType = PositionAnchorType::OffsetInAnchor;
candidateOffset =
layoutObject->caretMaxOffset() + text->textStartOffset();
nextNodeItreator = Strategy::next(*nextNodeItreator, startBlock);
} else if (editingIgnoresContent(*nextNodeItreator) ||
isDisplayInsideTable(nextNodeItreator)) {
candidateNode = nextNodeItreator;
candidateType = PositionAnchorType::AfterAnchor;
nextNodeItreator =
Strategy::nextSkippingChildren(*nextNodeItreator, startBlock);
} else {
nextNodeItreator = Strategy::next(*nextNodeItreator, startBlock);
}
}
if (candidateType == PositionAnchorType::OffsetInAnchor)
return PositionTemplate<Strategy>(candidateNode, candidateOffset);
return PositionTemplate<Strategy>(candidateNode, candidateType);
}
template <typename Strategy>
static VisiblePositionTemplate<Strategy> endOfParagraphAlgorithm(
const VisiblePositionTemplate<Strategy>& visiblePosition,
EditingBoundaryCrossingRule boundaryCrossingRule) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
return createVisiblePosition(endOfParagraphAlgorithm(
visiblePosition.deepEquivalent(), boundaryCrossingRule));
}
VisiblePosition endOfParagraph(
const VisiblePosition& c,
EditingBoundaryCrossingRule boundaryCrossingRule) {
return endOfParagraphAlgorithm<EditingStrategy>(c, boundaryCrossingRule);
}
VisiblePositionInFlatTree endOfParagraph(
const VisiblePositionInFlatTree& c,
EditingBoundaryCrossingRule boundaryCrossingRule) {
return endOfParagraphAlgorithm<EditingInFlatTreeStrategy>(
c, boundaryCrossingRule);
}
// FIXME: isStartOfParagraph(startOfNextParagraph(pos)) is not always true
VisiblePosition startOfNextParagraph(const VisiblePosition& visiblePosition) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
VisiblePosition paragraphEnd(
endOfParagraph(visiblePosition, CanSkipOverEditingBoundary));
VisiblePosition afterParagraphEnd(
nextPositionOf(paragraphEnd, CannotCrossEditingBoundary));
// The position after the last position in the last cell of a table
// is not the start of the next paragraph.
if (tableElementJustBefore(afterParagraphEnd))
return nextPositionOf(afterParagraphEnd, CannotCrossEditingBoundary);
return afterParagraphEnd;
}
// FIXME: isStartOfParagraph(startOfNextParagraph(pos)) is not always true
bool inSameParagraph(const VisiblePosition& a,
const VisiblePosition& b,
EditingBoundaryCrossingRule boundaryCrossingRule) {
DCHECK(a.isValid()) << a;
DCHECK(b.isValid()) << b;
return a.isNotNull() &&
startOfParagraph(a, boundaryCrossingRule).deepEquivalent() ==
startOfParagraph(b, boundaryCrossingRule).deepEquivalent();
}
template <typename Strategy>
static bool isStartOfParagraphAlgorithm(
const VisiblePositionTemplate<Strategy>& pos,
EditingBoundaryCrossingRule boundaryCrossingRule) {
DCHECK(pos.isValid()) << pos;
return pos.isNotNull() &&
pos.deepEquivalent() ==
startOfParagraph(pos, boundaryCrossingRule).deepEquivalent();
}
bool isStartOfParagraph(const VisiblePosition& pos,
EditingBoundaryCrossingRule boundaryCrossingRule) {
return isStartOfParagraphAlgorithm<EditingStrategy>(pos,
boundaryCrossingRule);
}
bool isStartOfParagraph(const VisiblePositionInFlatTree& pos,
EditingBoundaryCrossingRule boundaryCrossingRule) {
return isStartOfParagraphAlgorithm<EditingInFlatTreeStrategy>(
pos, boundaryCrossingRule);
}
template <typename Strategy>
static bool isEndOfParagraphAlgorithm(
const VisiblePositionTemplate<Strategy>& pos,
EditingBoundaryCrossingRule boundaryCrossingRule) {
DCHECK(pos.isValid()) << pos;
return pos.isNotNull() &&
pos.deepEquivalent() ==
endOfParagraph(pos, boundaryCrossingRule).deepEquivalent();
}
bool isEndOfParagraph(const VisiblePosition& pos,
EditingBoundaryCrossingRule boundaryCrossingRule) {
return isEndOfParagraphAlgorithm<EditingStrategy>(pos, boundaryCrossingRule);
}
bool isEndOfParagraph(const VisiblePositionInFlatTree& pos,
EditingBoundaryCrossingRule boundaryCrossingRule) {
return isEndOfParagraphAlgorithm<EditingInFlatTreeStrategy>(
pos, boundaryCrossingRule);
}
VisiblePosition previousParagraphPosition(const VisiblePosition& p,
LayoutUnit x) {
DCHECK(p.isValid()) << p;
VisiblePosition pos = p;
do {
VisiblePosition n = previousLinePosition(pos, x);
if (n.isNull() || n.deepEquivalent() == pos.deepEquivalent())
break;
pos = n;
} while (inSameParagraph(p, pos));
return pos;
}
VisiblePosition nextParagraphPosition(const VisiblePosition& p, LayoutUnit x) {
DCHECK(p.isValid()) << p;
VisiblePosition pos = p;
do {
VisiblePosition n = nextLinePosition(pos, x);
if (n.isNull() || n.deepEquivalent() == pos.deepEquivalent())
break;
pos = n;
} while (inSameParagraph(p, pos));
return pos;
}
// ---------
VisiblePosition startOfBlock(const VisiblePosition& visiblePosition,
EditingBoundaryCrossingRule rule) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
Position position = visiblePosition.deepEquivalent();
Element* startBlock =
position.computeContainerNode()
? enclosingBlock(position.computeContainerNode(), rule)
: 0;
return startBlock ? VisiblePosition::firstPositionInNode(startBlock)
: VisiblePosition();
}
VisiblePosition endOfBlock(const VisiblePosition& visiblePosition,
EditingBoundaryCrossingRule rule) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
Position position = visiblePosition.deepEquivalent();
Element* endBlock =
position.computeContainerNode()
? enclosingBlock(position.computeContainerNode(), rule)
: 0;
return endBlock ? VisiblePosition::lastPositionInNode(endBlock)
: VisiblePosition();
}
bool inSameBlock(const VisiblePosition& a, const VisiblePosition& b) {
DCHECK(a.isValid()) << a;
DCHECK(b.isValid()) << b;
return !a.isNull() &&
enclosingBlock(a.deepEquivalent().computeContainerNode()) ==
enclosingBlock(b.deepEquivalent().computeContainerNode());
}
bool isStartOfBlock(const VisiblePosition& pos) {
DCHECK(pos.isValid()) << pos;
return pos.isNotNull() &&
pos.deepEquivalent() ==
startOfBlock(pos, CanCrossEditingBoundary).deepEquivalent();
}
bool isEndOfBlock(const VisiblePosition& pos) {
DCHECK(pos.isValid()) << pos;
return pos.isNotNull() &&
pos.deepEquivalent() ==
endOfBlock(pos, CanCrossEditingBoundary).deepEquivalent();
}
// ---------
template <typename Strategy>
static VisiblePositionTemplate<Strategy> startOfDocumentAlgorithm(
const VisiblePositionTemplate<Strategy>& visiblePosition) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
Node* node = visiblePosition.deepEquivalent().anchorNode();
if (!node || !node->document().documentElement())
return VisiblePositionTemplate<Strategy>();
return createVisiblePosition(PositionTemplate<Strategy>::firstPositionInNode(
node->document().documentElement()));
}
VisiblePosition startOfDocument(const VisiblePosition& c) {
return startOfDocumentAlgorithm<EditingStrategy>(c);
}
VisiblePositionInFlatTree startOfDocument(const VisiblePositionInFlatTree& c) {
return startOfDocumentAlgorithm<EditingInFlatTreeStrategy>(c);
}
template <typename Strategy>
static VisiblePositionTemplate<Strategy> endOfDocumentAlgorithm(
const VisiblePositionTemplate<Strategy>& visiblePosition) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
Node* node = visiblePosition.deepEquivalent().anchorNode();
if (!node || !node->document().documentElement())
return VisiblePositionTemplate<Strategy>();
Element* doc = node->document().documentElement();
return createVisiblePosition(
PositionTemplate<Strategy>::lastPositionInNode(doc));
}
VisiblePosition endOfDocument(const VisiblePosition& c) {
return endOfDocumentAlgorithm<EditingStrategy>(c);
}
VisiblePositionInFlatTree endOfDocument(const VisiblePositionInFlatTree& c) {
return endOfDocumentAlgorithm<EditingInFlatTreeStrategy>(c);
}
bool isStartOfDocument(const VisiblePosition& p) {
DCHECK(p.isValid()) << p;
return p.isNotNull() &&
previousPositionOf(p, CanCrossEditingBoundary).isNull();
}
bool isEndOfDocument(const VisiblePosition& p) {
DCHECK(p.isValid()) << p;
return p.isNotNull() && nextPositionOf(p, CanCrossEditingBoundary).isNull();
}
// ---------
VisiblePosition startOfEditableContent(const VisiblePosition& visiblePosition) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
ContainerNode* highestRoot =
highestEditableRoot(visiblePosition.deepEquivalent());
if (!highestRoot)
return VisiblePosition();
return VisiblePosition::firstPositionInNode(highestRoot);
}
VisiblePosition endOfEditableContent(const VisiblePosition& visiblePosition) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
ContainerNode* highestRoot =
highestEditableRoot(visiblePosition.deepEquivalent());
if (!highestRoot)
return VisiblePosition();
return VisiblePosition::lastPositionInNode(highestRoot);
}
bool isEndOfEditableOrNonEditableContent(const VisiblePosition& position) {
DCHECK(position.isValid()) << position;
return position.isNotNull() && nextPositionOf(position).isNull();
}
// TODO(yosin) We should rename |isEndOfEditableOrNonEditableContent()| what
// this function does, e.g. |isLastVisiblePositionOrEndOfInnerEditor()|.
bool isEndOfEditableOrNonEditableContent(
const VisiblePositionInFlatTree& position) {
DCHECK(position.isValid()) << position;
if (position.isNull())
return false;
const VisiblePositionInFlatTree nextPosition = nextPositionOf(position);
if (nextPosition.isNull())
return true;
// In DOM version, following condition, the last position of inner editor
// of INPUT/TEXTAREA element, by |nextPosition().isNull()|, because of
// an inner editor is an only leaf node.
if (!nextPosition.deepEquivalent().isAfterAnchor())
return false;
return isTextControlElement(nextPosition.deepEquivalent().anchorNode());
}
VisiblePosition leftBoundaryOfLine(const VisiblePosition& c,
TextDirection direction) {
DCHECK(c.isValid()) << c;
return direction == TextDirection::Ltr ? logicalStartOfLine(c)
: logicalEndOfLine(c);
}
VisiblePosition rightBoundaryOfLine(const VisiblePosition& c,
TextDirection direction) {
DCHECK(c.isValid()) << c;
return direction == TextDirection::Ltr ? logicalEndOfLine(c)
: logicalStartOfLine(c);
}
static bool isNonTextLeafChild(LayoutObject* object) {
if (object->slowFirstChild())
return false;
if (object->isText())
return false;
return true;
}
static InlineTextBox* searchAheadForBetterMatch(LayoutObject* layoutObject) {
LayoutBlock* container = layoutObject->containingBlock();
for (LayoutObject* next = layoutObject->nextInPreOrder(container); next;
next = next->nextInPreOrder(container)) {
if (next->isLayoutBlock())
return 0;
if (next->isBR())
return 0;
if (isNonTextLeafChild(next))
return 0;
if (next->isText()) {
InlineTextBox* match = 0;
int minOffset = INT_MAX;
for (InlineTextBox* box = toLayoutText(next)->firstTextBox(); box;
box = box->nextTextBox()) {
int caretMinOffset = box->caretMinOffset();
if (caretMinOffset < minOffset) {
match = box;
minOffset = caretMinOffset;
}
}
if (match)
return match;
}
}
return 0;
}
template <typename Strategy>
PositionTemplate<Strategy> downstreamIgnoringEditingBoundaries(
PositionTemplate<Strategy> position) {
PositionTemplate<Strategy> lastPosition;
while (position != lastPosition) {
lastPosition = position;
position = mostForwardCaretPosition(position, CanCrossEditingBoundary);
}
return position;
}
template <typename Strategy>
PositionTemplate<Strategy> upstreamIgnoringEditingBoundaries(
PositionTemplate<Strategy> position) {
PositionTemplate<Strategy> lastPosition;
while (position != lastPosition) {
lastPosition = position;
position = mostBackwardCaretPosition(position, CanCrossEditingBoundary);
}
return position;
}
// Returns true if |inlineBox| starts different direction of embedded text ru.
// See [1] for details.
// [1] UNICODE BIDIRECTIONAL ALGORITHM, http://unicode.org/reports/tr9/
static bool isStartOfDifferentDirection(const InlineBox* inlineBox) {
InlineBox* prevBox = inlineBox->prevLeafChild();
if (!prevBox)
return true;
if (prevBox->direction() == inlineBox->direction())
return true;
DCHECK_NE(prevBox->bidiLevel(), inlineBox->bidiLevel());
return prevBox->bidiLevel() > inlineBox->bidiLevel();
}
template <typename Strategy>
static InlineBoxPosition computeInlineBoxPositionTemplate(
const PositionTemplate<Strategy>& position,
TextAffinity affinity,
TextDirection primaryDirection) {
InlineBox* inlineBox = nullptr;
int caretOffset = position.computeEditingOffset();
Node* const anchorNode = position.anchorNode();
LayoutObject* layoutObject =
anchorNode->isShadowRoot()
? toShadowRoot(anchorNode)->host().layoutObject()
: anchorNode->layoutObject();
DCHECK(layoutObject) << position;
if (!layoutObject->isText()) {
inlineBox = 0;
if (canHaveChildrenForEditing(anchorNode) &&
layoutObject->isLayoutBlockFlow() &&
hasRenderedNonAnonymousDescendantsWithHeight(layoutObject)) {
// Try a visually equivalent position with possibly opposite
// editability. This helps in case |this| is in an editable block
// but surrounded by non-editable positions. It acts to negate the
// logic at the beginning of
// |LayoutObject::createPositionWithAffinity()|.
PositionTemplate<Strategy> equivalent =
downstreamIgnoringEditingBoundaries(position);
if (equivalent == position) {
equivalent = upstreamIgnoringEditingBoundaries(position);
if (equivalent == position ||
downstreamIgnoringEditingBoundaries(equivalent) == position)
return InlineBoxPosition(inlineBox, caretOffset);
}
return computeInlineBoxPosition(equivalent, TextAffinity::Upstream,
primaryDirection);
}
if (layoutObject->isBox()) {
inlineBox = toLayoutBox(layoutObject)->inlineBoxWrapper();
if (!inlineBox || (caretOffset > inlineBox->caretMinOffset() &&
caretOffset < inlineBox->caretMaxOffset()))
return InlineBoxPosition(inlineBox, caretOffset);
}
} else {
LayoutText* textLayoutObject = toLayoutText(layoutObject);
InlineTextBox* box;
InlineTextBox* candidate = 0;
for (box = textLayoutObject->firstTextBox(); box;
box = box->nextTextBox()) {
int caretMinOffset = box->caretMinOffset();
int caretMaxOffset = box->caretMaxOffset();
if (caretOffset < caretMinOffset || caretOffset > caretMaxOffset ||
(caretOffset == caretMaxOffset && box->isLineBreak()))
continue;
if (caretOffset > caretMinOffset && caretOffset < caretMaxOffset)
return InlineBoxPosition(box, caretOffset);
if (((caretOffset == caretMaxOffset) ^
(affinity == TextAffinity::Downstream)) ||
((caretOffset == caretMinOffset) ^
(affinity == TextAffinity::Upstream)) ||
(caretOffset == caretMaxOffset && box->nextLeafChild() &&
box->nextLeafChild()->isLineBreak()))
break;
candidate = box;
}
if (candidate && candidate == textLayoutObject->lastTextBox() &&
affinity == TextAffinity::Downstream) {
box = searchAheadForBetterMatch(textLayoutObject);
if (box)
caretOffset = box->caretMinOffset();
}
inlineBox = box ? box : candidate;
}
if (!inlineBox)
return InlineBoxPosition(inlineBox, caretOffset);
unsigned char level = inlineBox->bidiLevel();
if (inlineBox->direction() == primaryDirection) {
if (caretOffset == inlineBox->caretRightmostOffset()) {
InlineBox* nextBox = inlineBox->nextLeafChild();
if (!nextBox || nextBox->bidiLevel() >= level)
return InlineBoxPosition(inlineBox, caretOffset);
level = nextBox->bidiLevel();
InlineBox* prevBox = inlineBox;
do {
prevBox = prevBox->prevLeafChild();
} while (prevBox && prevBox->bidiLevel() > level);
// For example, abc FED 123 ^ CBA
if (prevBox && prevBox->bidiLevel() == level)
return InlineBoxPosition(inlineBox, caretOffset);
// For example, abc 123 ^ CBA
while (InlineBox* nextBox = inlineBox->nextLeafChild()) {
if (nextBox->bidiLevel() < level)
break;
inlineBox = nextBox;
}
return InlineBoxPosition(inlineBox, inlineBox->caretRightmostOffset());
}
if (isStartOfDifferentDirection(inlineBox))
return InlineBoxPosition(inlineBox, caretOffset);
level = inlineBox->prevLeafChild()->bidiLevel();
InlineBox* nextBox = inlineBox;
do {
nextBox = nextBox->nextLeafChild();
} while (nextBox && nextBox->bidiLevel() > level);
if (nextBox && nextBox->bidiLevel() == level)
return InlineBoxPosition(inlineBox, caretOffset);
while (InlineBox* prevBox = inlineBox->prevLeafChild()) {
if (prevBox->bidiLevel() < level)
break;
inlineBox = prevBox;
}
return InlineBoxPosition(inlineBox, inlineBox->caretLeftmostOffset());
}
if (caretOffset == inlineBox->caretLeftmostOffset()) {
InlineBox* prevBox = inlineBox->prevLeafChildIgnoringLineBreak();
if (!prevBox || prevBox->bidiLevel() < level) {
// Left edge of a secondary run. Set to the right edge of the entire
// run.
while (InlineBox* nextBox = inlineBox->nextLeafChildIgnoringLineBreak()) {
if (nextBox->bidiLevel() < level)
break;
inlineBox = nextBox;
}
return InlineBoxPosition(inlineBox, inlineBox->caretRightmostOffset());
}
if (prevBox->bidiLevel() > level) {
// Right edge of a "tertiary" run. Set to the left edge of that run.
while (InlineBox* tertiaryBox =
inlineBox->prevLeafChildIgnoringLineBreak()) {
if (tertiaryBox->bidiLevel() <= level)
break;
inlineBox = tertiaryBox;
}
return InlineBoxPosition(inlineBox, inlineBox->caretLeftmostOffset());
}
return InlineBoxPosition(inlineBox, caretOffset);
}
if (layoutObject && layoutObject->style()->unicodeBidi() == Plaintext) {
if (inlineBox->bidiLevel() < level)
return InlineBoxPosition(inlineBox, inlineBox->caretLeftmostOffset());
return InlineBoxPosition(inlineBox, inlineBox->caretRightmostOffset());
}
InlineBox* nextBox = inlineBox->nextLeafChildIgnoringLineBreak();
if (!nextBox || nextBox->bidiLevel() < level) {
// Right edge of a secondary run. Set to the left edge of the entire
// run.
while (InlineBox* prevBox = inlineBox->prevLeafChildIgnoringLineBreak()) {
if (prevBox->bidiLevel() < level)
break;
inlineBox = prevBox;
}
return InlineBoxPosition(inlineBox, inlineBox->caretLeftmostOffset());
}
if (nextBox->bidiLevel() <= level)
return InlineBoxPosition(inlineBox, caretOffset);
// Left edge of a "tertiary" run. Set to the right edge of that run.
while (InlineBox* tertiaryBox = inlineBox->nextLeafChildIgnoringLineBreak()) {
if (tertiaryBox->bidiLevel() <= level)
break;
inlineBox = tertiaryBox;
}
return InlineBoxPosition(inlineBox, inlineBox->caretRightmostOffset());
}
template <typename Strategy>
static InlineBoxPosition computeInlineBoxPositionTemplate(
const PositionTemplate<Strategy>& position,
TextAffinity affinity) {
return computeInlineBoxPositionTemplate<Strategy>(
position, affinity, primaryDirectionOf(*position.anchorNode()));
}
InlineBoxPosition computeInlineBoxPosition(const Position& position,
TextAffinity affinity) {
return computeInlineBoxPositionTemplate<EditingStrategy>(position, affinity);
}
InlineBoxPosition computeInlineBoxPosition(const PositionInFlatTree& position,
TextAffinity affinity) {
return computeInlineBoxPositionTemplate<EditingInFlatTreeStrategy>(position,
affinity);
}
InlineBoxPosition computeInlineBoxPosition(const VisiblePosition& position) {
DCHECK(position.isValid()) << position;
return computeInlineBoxPosition(position.deepEquivalent(),
position.affinity());
}
InlineBoxPosition computeInlineBoxPosition(
const VisiblePositionInFlatTree& position) {
DCHECK(position.isValid()) << position;
return computeInlineBoxPosition(position.deepEquivalent(),
position.affinity());
}
InlineBoxPosition computeInlineBoxPosition(const Position& position,
TextAffinity affinity,
TextDirection primaryDirection) {
return computeInlineBoxPositionTemplate<EditingStrategy>(position, affinity,
primaryDirection);
}
InlineBoxPosition computeInlineBoxPosition(const PositionInFlatTree& position,
TextAffinity affinity,
TextDirection primaryDirection) {
return computeInlineBoxPositionTemplate<EditingInFlatTreeStrategy>(
position, affinity, primaryDirection);
}
template <typename Strategy>
LayoutRect localCaretRectOfPositionTemplate(
const PositionWithAffinityTemplate<Strategy>& position,
LayoutObject*& layoutObject) {
if (position.isNull()) {
layoutObject = nullptr;
return LayoutRect();
}
Node* node = position.anchorNode();
layoutObject = node->layoutObject();
if (!layoutObject)
return LayoutRect();
InlineBoxPosition boxPosition =
computeInlineBoxPosition(position.position(), position.affinity());
if (boxPosition.inlineBox)
layoutObject = LineLayoutAPIShim::layoutObjectFrom(
boxPosition.inlineBox->getLineLayoutItem());
return layoutObject->localCaretRect(boxPosition.inlineBox,
boxPosition.offsetInBox);
}
LayoutRect localCaretRectOfPosition(const PositionWithAffinity& position,
LayoutObject*& layoutObject) {
return localCaretRectOfPositionTemplate<EditingStrategy>(position,
layoutObject);
}
LayoutRect localCaretRectOfPosition(
const PositionInFlatTreeWithAffinity& position,
LayoutObject*& layoutObject) {
return localCaretRectOfPositionTemplate<EditingInFlatTreeStrategy>(
position, layoutObject);
}
static LayoutUnit boundingBoxLogicalHeight(LayoutObject* o,
const LayoutRect& rect) {
return o->style()->isHorizontalWritingMode() ? rect.height() : rect.width();
}
bool hasRenderedNonAnonymousDescendantsWithHeight(LayoutObject* layoutObject) {
LayoutObject* stop = layoutObject->nextInPreOrderAfterChildren();
for (LayoutObject* o = layoutObject->slowFirstChild(); o && o != stop;
o = o->nextInPreOrder()) {
if (o->nonPseudoNode()) {
if ((o->isText() &&
boundingBoxLogicalHeight(o, toLayoutText(o)->linesBoundingBox())) ||
(o->isBox() && toLayoutBox(o)->pixelSnappedLogicalHeight()) ||
(o->isLayoutInline() && isEmptyInline(LineLayoutItem(o)) &&
boundingBoxLogicalHeight(o, toLayoutInline(o)->linesBoundingBox())))
return true;
}
}
return false;
}
VisiblePosition visiblePositionForContentsPoint(const IntPoint& contentsPoint,
LocalFrame* frame) {
HitTestRequest request = HitTestRequest::Move | HitTestRequest::ReadOnly |
HitTestRequest::Active |
HitTestRequest::IgnoreClipping;
HitTestResult result(request, contentsPoint);
frame->document()->layoutViewItem().hitTest(result);
if (Node* node = result.innerNode())
return createVisiblePosition(positionRespectingEditingBoundary(
frame->selection().selection().start(), result.localPoint(), node));
return VisiblePosition();
}
// TODO(yosin): We should use |associatedLayoutObjectOf()| in "VisibleUnits.cpp"
// where it takes |LayoutObject| from |Position|.
// Note about ::first-letter pseudo-element:
// When an element has ::first-letter pseudo-element, first letter characters
// are taken from |Text| node and first letter characters are considered
// as content of <pseudo:first-letter>.
// For following HTML,
// <style>div::first-letter {color: red}</style>
// <div>abc</div>
// we have following layout tree:
// LayoutBlockFlow {DIV} at (0,0) size 784x55
// LayoutInline {<pseudo:first-letter>} at (0,0) size 22x53
// LayoutTextFragment (anonymous) at (0,1) size 22x53
// text run at (0,1) width 22: "a"
// LayoutTextFragment {#text} at (21,30) size 16x17
// text run at (21,30) width 16: "bc"
// In this case, |Text::layoutObject()| for "abc" returns |LayoutTextFragment|
// containing "bc", and it is called remaining part.
//
// Even if |Text| node contains only first-letter characters, e.g. just "a",
// remaining part of |LayoutTextFragment|, with |fragmentLength()| == 0, is
// appeared in layout tree.
//
// When |Text| node contains only first-letter characters and whitespaces, e.g.
// "B\n", associated |LayoutTextFragment| is first-letter part instead of
// remaining part.
//
// Punctuation characters are considered as first-letter. For "(1)ab",
// "(1)" are first-letter part and "ab" are remaining part.
LayoutObject* associatedLayoutObjectOf(const Node& node, int offsetInNode) {
DCHECK_GE(offsetInNode, 0);
LayoutObject* layoutObject = node.layoutObject();
if (!node.isTextNode() || !layoutObject ||
!toLayoutText(layoutObject)->isTextFragment())
return layoutObject;
LayoutTextFragment* layoutTextFragment = toLayoutTextFragment(layoutObject);
if (!layoutTextFragment->isRemainingTextLayoutObject()) {
DCHECK_LE(
static_cast<unsigned>(offsetInNode),
layoutTextFragment->start() + layoutTextFragment->fragmentLength());
return layoutTextFragment;
}
if (layoutTextFragment->fragmentLength() &&
static_cast<unsigned>(offsetInNode) >= layoutTextFragment->start())
return layoutObject;
LayoutObject* firstLetterLayoutObject =
layoutTextFragment->firstLetterPseudoElement()->layoutObject();
// TODO(yosin): We're not sure when |firstLetterLayoutObject| has
// multiple child layout object.
DCHECK_EQ(firstLetterLayoutObject->slowFirstChild(),
firstLetterLayoutObject->slowLastChild());
return firstLetterLayoutObject->slowFirstChild();
}
int caretMinOffset(const Node* node) {
LayoutObject* layoutObject = associatedLayoutObjectOf(*node, 0);
return layoutObject ? layoutObject->caretMinOffset() : 0;
}
int caretMaxOffset(const Node* n) {
return EditingStrategy::caretMaxOffset(*n);
}
template <typename Strategy>
static bool inRenderedText(const PositionTemplate<Strategy>& position) {
Node* const anchorNode = position.anchorNode();
if (!anchorNode || !anchorNode->isTextNode())
return false;
const int offsetInNode = position.computeEditingOffset();
LayoutObject* layoutObject =
associatedLayoutObjectOf(*anchorNode, offsetInNode);
if (!layoutObject)
return false;
LayoutText* textLayoutObject = toLayoutText(layoutObject);
const int textOffset = offsetInNode - textLayoutObject->textStartOffset();
for (InlineTextBox* box = textLayoutObject->firstTextBox(); box;
box = box->nextTextBox()) {
if (textOffset < static_cast<int>(box->start()) &&
!textLayoutObject->containsReversedText()) {
// The offset we're looking for is before this node
// this means the offset must be in content that is
// not laid out. Return false.
return false;
}
if (box->containsCaretOffset(textOffset)) {
// Return false for offsets inside composed characters.
return textOffset == 0 ||
textOffset == nextGraphemeBoundaryOf(anchorNode,
previousGraphemeBoundaryOf(
anchorNode, textOffset));
}
}
return false;
}
bool rendersInDifferentPosition(const Position& position1,
const Position& position2) {
if (position1.isNull() || position2.isNull())
return false;
LayoutObject* layoutObject1;
const LayoutRect& rect1 =
localCaretRectOfPosition(PositionWithAffinity(position1), layoutObject1);
LayoutObject* layoutObject2;
const LayoutRect& rect2 =
localCaretRectOfPosition(PositionWithAffinity(position2), layoutObject2);
if (!layoutObject1 || !layoutObject2)
return layoutObject1 != layoutObject2;
return layoutObject1->localToAbsoluteQuad(FloatRect(rect1)) !=
layoutObject2->localToAbsoluteQuad(FloatRect(rect2));
}
static bool isVisuallyEmpty(const LayoutObject* layout) {
for (LayoutObject* child = layout->slowFirstChild(); child;
child = child->nextSibling()) {
// TODO(xiaochengh): Replace type-based conditioning by virtual function.
if (child->isBox()) {
if (!toLayoutBox(child)->size().isEmpty())
return false;
} else if (child->isLayoutInline()) {
if (toLayoutInline(child)->firstLineBoxIncludingCulling())
return false;
} else if (child->isText()) {
if (toLayoutText(child)->hasTextBoxes())
return false;
} else {
return false;
}
}
return true;
}
// FIXME: Share code with isCandidate, if possible.
bool endsOfNodeAreVisuallyDistinctPositions(const Node* node) {
if (!node || !node->layoutObject())
return false;
if (!node->layoutObject()->isInline())
return true;
// Don't include inline tables.
if (isHTMLTableElement(*node))
return false;
// A Marquee elements are moving so we should assume their ends are always
// visibily distinct.
if (isHTMLMarqueeElement(*node))
return true;
// There is a VisiblePosition inside an empty inline-block container.
return node->layoutObject()->isAtomicInlineLevel() &&
canHaveChildrenForEditing(node) &&
!toLayoutBox(node->layoutObject())->size().isEmpty() &&
isVisuallyEmpty(node->layoutObject());
}
template <typename Strategy>
static Node* enclosingVisualBoundary(Node* node) {
while (node && !endsOfNodeAreVisuallyDistinctPositions(node))
node = Strategy::parent(*node);
return node;
}
// upstream() and downstream() want to return positions that are either in a
// text node or at just before a non-text node. This method checks for that.
template <typename Strategy>
static bool isStreamer(const PositionIteratorAlgorithm<Strategy>& pos) {
if (!pos.node())
return true;
if (isAtomicNode(pos.node()))
return true;
return pos.atStartOfNode();
}
template <typename Strategy>
static PositionTemplate<Strategy> mostBackwardCaretPosition(
const PositionTemplate<Strategy>& position,
EditingBoundaryCrossingRule rule) {
TRACE_EVENT0("input", "VisibleUnits::mostBackwardCaretPosition");
Node* startNode = position.anchorNode();
if (!startNode)
return PositionTemplate<Strategy>();
// iterate backward from there, looking for a qualified position
Node* boundary = enclosingVisualBoundary<Strategy>(startNode);
// FIXME: PositionIterator should respect Before and After positions.
PositionIteratorAlgorithm<Strategy> lastVisible(
position.isAfterAnchor()
? PositionTemplate<Strategy>::editingPositionOf(
position.anchorNode(),
Strategy::caretMaxOffset(*position.anchorNode()))
: position);
PositionIteratorAlgorithm<Strategy> currentPos = lastVisible;
bool startEditable = hasEditableStyle(*startNode);
Node* lastNode = startNode;
bool boundaryCrossed = false;
for (; !currentPos.atStart(); currentPos.decrement()) {
Node* currentNode = currentPos.node();
// Don't check for an editability change if we haven't moved to a different
// node, to avoid the expense of computing hasEditableStyle().
if (currentNode != lastNode) {
// Don't change editability.
bool currentEditable = hasEditableStyle(*currentNode);
if (startEditable != currentEditable) {
if (rule == CannotCrossEditingBoundary)
break;
boundaryCrossed = true;
}
lastNode = currentNode;
}
// If we've moved to a position that is visually distinct, return the last
// saved position. There is code below that terminates early if we're
// *about* to move to a visually distinct position.
if (endsOfNodeAreVisuallyDistinctPositions(currentNode) &&
currentNode != boundary)
return lastVisible.deprecatedComputePosition();
// skip position in non-laid out or invisible node
LayoutObject* layoutObject =
associatedLayoutObjectOf(*currentNode, currentPos.offsetInLeafNode());
if (!layoutObject ||
layoutObject->style()->visibility() != EVisibility::Visible)
continue;
if (rule == CanCrossEditingBoundary && boundaryCrossed) {
lastVisible = currentPos;
break;
}
// track last visible streamer position
if (isStreamer<Strategy>(currentPos))
lastVisible = currentPos;
// Don't move past a position that is visually distinct. We could rely on
// code above to terminate and return lastVisible on the next iteration, but
// we terminate early to avoid doing a nodeIndex() call.
if (endsOfNodeAreVisuallyDistinctPositions(currentNode) &&
currentPos.atStartOfNode())
return lastVisible.deprecatedComputePosition();
// Return position after tables and nodes which have content that can be
// ignored.
if (editingIgnoresContent(*currentNode) ||
isDisplayInsideTable(currentNode)) {
if (currentPos.atEndOfNode())
return PositionTemplate<Strategy>::afterNode(currentNode);
continue;
}
// return current position if it is in laid out text
if (layoutObject->isText() && toLayoutText(layoutObject)->firstTextBox()) {
LayoutText* const textLayoutObject = toLayoutText(layoutObject);
const unsigned textStartOffset = textLayoutObject->textStartOffset();
if (currentNode != startNode) {
// This assertion fires in layout tests in the case-transform.html test
// because of a mix-up between offsets in the text in the DOM tree with
// text in the layout tree which can have a different length due to case
// transformation.
// Until we resolve that, disable this so we can run the layout tests!
// DCHECK_GE(currentOffset, layoutObject->caretMaxOffset());
return PositionTemplate<Strategy>(
currentNode, layoutObject->caretMaxOffset() + textStartOffset);
}
// Map offset in DOM node to offset in InlineBox.
DCHECK_GE(currentPos.offsetInLeafNode(),
static_cast<int>(textStartOffset));
const unsigned textOffset =
currentPos.offsetInLeafNode() - textStartOffset;
InlineTextBox* lastTextBox = textLayoutObject->lastTextBox();
for (InlineTextBox* box = textLayoutObject->firstTextBox(); box;
box = box->nextTextBox()) {
if (textOffset == box->start()) {
if (textLayoutObject->isTextFragment() &&
toLayoutTextFragment(layoutObject)
->isRemainingTextLayoutObject()) {
// |currentPos| is at start of remaining text of
// |Text| node with :first-letter.
DCHECK_GE(currentPos.offsetInLeafNode(), 1);
LayoutObject* firstLetterLayoutObject =
toLayoutTextFragment(layoutObject)
->firstLetterPseudoElement()
->layoutObject();
if (firstLetterLayoutObject &&
firstLetterLayoutObject->style()->visibility() ==
EVisibility::Visible)
return currentPos.computePosition();
}
continue;
}
if (textOffset <= box->start() + box->len()) {
if (textOffset > box->start())
return currentPos.computePosition();
continue;
}
if (box == lastTextBox || textOffset != box->start() + box->len() + 1)
continue;
// The text continues on the next line only if the last text box is not
// on this line and none of the boxes on this line have a larger start
// offset.
bool continuesOnNextLine = true;
InlineBox* otherBox = box;
while (continuesOnNextLine) {
otherBox = otherBox->nextLeafChild();
if (!otherBox)
break;
if (otherBox == lastTextBox ||
(LineLayoutAPIShim::layoutObjectFrom(
otherBox->getLineLayoutItem()) == textLayoutObject &&
toInlineTextBox(otherBox)->start() > textOffset))
continuesOnNextLine = false;
}
otherBox = box;
while (continuesOnNextLine) {
otherBox = otherBox->prevLeafChild();
if (!otherBox)
break;
if (otherBox == lastTextBox ||
(LineLayoutAPIShim::layoutObjectFrom(
otherBox->getLineLayoutItem()) == textLayoutObject &&
toInlineTextBox(otherBox)->start() > textOffset))
continuesOnNextLine = false;
}
if (continuesOnNextLine)
return currentPos.computePosition();
}
}
}
return lastVisible.deprecatedComputePosition();
}
Position mostBackwardCaretPosition(const Position& position,
EditingBoundaryCrossingRule rule) {
return mostBackwardCaretPosition<EditingStrategy>(position, rule);
}
PositionInFlatTree mostBackwardCaretPosition(const PositionInFlatTree& position,
EditingBoundaryCrossingRule rule) {
return mostBackwardCaretPosition<EditingInFlatTreeStrategy>(position, rule);
}
template <typename Strategy>
PositionTemplate<Strategy> mostForwardCaretPosition(
const PositionTemplate<Strategy>& position,
EditingBoundaryCrossingRule rule) {
TRACE_EVENT0("input", "VisibleUnits::mostForwardCaretPosition");
Node* startNode = position.anchorNode();
if (!startNode)
return PositionTemplate<Strategy>();
// iterate forward from there, looking for a qualified position
Node* boundary = enclosingVisualBoundary<Strategy>(startNode);
// FIXME: PositionIterator should respect Before and After positions.
PositionIteratorAlgorithm<Strategy> lastVisible(
position.isAfterAnchor()
? PositionTemplate<Strategy>::editingPositionOf(
position.anchorNode(),
Strategy::caretMaxOffset(*position.anchorNode()))
: position);
PositionIteratorAlgorithm<Strategy> currentPos = lastVisible;
bool startEditable = hasEditableStyle(*startNode);
Node* lastNode = startNode;
bool boundaryCrossed = false;
for (; !currentPos.atEnd(); currentPos.increment()) {
Node* currentNode = currentPos.node();
// Don't check for an editability change if we haven't moved to a different
// node, to avoid the expense of computing hasEditableStyle().
if (currentNode != lastNode) {
// Don't change editability.
bool currentEditable = hasEditableStyle(*currentNode);
if (startEditable != currentEditable) {
if (rule == CannotCrossEditingBoundary)
break;
boundaryCrossed = true;
}
lastNode = currentNode;
}
// stop before going above the body, up into the head
// return the last visible streamer position
if (isHTMLBodyElement(*currentNode) && currentPos.atEndOfNode())
break;
// Do not move to a visually distinct position.
if (endsOfNodeAreVisuallyDistinctPositions(currentNode) &&
currentNode != boundary)
return lastVisible.deprecatedComputePosition();
// Do not move past a visually disinct position.
// Note: The first position after the last in a node whose ends are visually
// distinct positions will be [boundary->parentNode(),
// originalBlock->nodeIndex() + 1].
if (boundary && Strategy::parent(*boundary) == currentNode)
return lastVisible.deprecatedComputePosition();
// skip position in non-laid out or invisible node
LayoutObject* layoutObject =
associatedLayoutObjectOf(*currentNode, currentPos.offsetInLeafNode());
if (!layoutObject ||
layoutObject->style()->visibility() != EVisibility::Visible)
continue;
if (rule == CanCrossEditingBoundary && boundaryCrossed) {
lastVisible = currentPos;
break;
}
// track last visible streamer position
if (isStreamer<Strategy>(currentPos))
lastVisible = currentPos;
// Return position before tables and nodes which have content that can be
// ignored.
if (editingIgnoresContent(*currentNode) ||
isDisplayInsideTable(currentNode)) {
if (currentPos.offsetInLeafNode() <= layoutObject->caretMinOffset())
return PositionTemplate<Strategy>::editingPositionOf(
currentNode, layoutObject->caretMinOffset());
continue;
}
// return current position if it is in laid out text
if (layoutObject->isText() && toLayoutText(layoutObject)->firstTextBox()) {
LayoutText* const textLayoutObject = toLayoutText(layoutObject);
const unsigned textStartOffset = textLayoutObject->textStartOffset();
if (currentNode != startNode) {
DCHECK(currentPos.atStartOfNode());
return PositionTemplate<Strategy>(
currentNode, layoutObject->caretMinOffset() + textStartOffset);
}
// Map offset in DOM node to offset in InlineBox.
DCHECK_GE(currentPos.offsetInLeafNode(),
static_cast<int>(textStartOffset));
const unsigned textOffset =
currentPos.offsetInLeafNode() - textStartOffset;
InlineTextBox* lastTextBox = textLayoutObject->lastTextBox();
for (InlineTextBox* box = textLayoutObject->firstTextBox(); box;
box = box->nextTextBox()) {
if (textOffset <= box->end()) {
if (textOffset >= box->start())
return currentPos.computePosition();
continue;
}
if (box == lastTextBox || textOffset != box->start() + box->len())
continue;
// The text continues on the next line only if the last text box is not
// on this line and none of the boxes on this line have a larger start
// offset.
bool continuesOnNextLine = true;
InlineBox* otherBox = box;
while (continuesOnNextLine) {
otherBox = otherBox->nextLeafChild();
if (!otherBox)
break;
if (otherBox == lastTextBox ||
(LineLayoutAPIShim::layoutObjectFrom(
otherBox->getLineLayoutItem()) == textLayoutObject &&
toInlineTextBox(otherBox)->start() >= textOffset))
continuesOnNextLine = false;
}
otherBox = box;
while (continuesOnNextLine) {
otherBox = otherBox->prevLeafChild();
if (!otherBox)
break;
if (otherBox == lastTextBox ||
(LineLayoutAPIShim::layoutObjectFrom(
otherBox->getLineLayoutItem()) == textLayoutObject &&
toInlineTextBox(otherBox)->start() >= textOffset))
continuesOnNextLine = false;
}
if (continuesOnNextLine)
return currentPos.computePosition();
}
}
}
return lastVisible.deprecatedComputePosition();
}
Position mostForwardCaretPosition(const Position& position,
EditingBoundaryCrossingRule rule) {
return mostForwardCaretPosition<EditingStrategy>(position, rule);
}
PositionInFlatTree mostForwardCaretPosition(const PositionInFlatTree& position,
EditingBoundaryCrossingRule rule) {
return mostForwardCaretPosition<EditingInFlatTreeStrategy>(position, rule);
}
// Returns true if the visually equivalent positions around have different
// editability. A position is considered at editing boundary if one of the
// following is true:
// 1. It is the first position in the node and the next visually equivalent
// position is non editable.
// 2. It is the last position in the node and the previous visually equivalent
// position is non editable.
// 3. It is an editable position and both the next and previous visually
// equivalent positions are both non editable.
template <typename Strategy>
static bool atEditingBoundary(const PositionTemplate<Strategy> positions) {
PositionTemplate<Strategy> nextPosition =
mostForwardCaretPosition(positions, CanCrossEditingBoundary);
if (positions.atFirstEditingPositionForNode() && nextPosition.isNotNull() &&
!hasEditableStyle(*nextPosition.anchorNode()))
return true;
PositionTemplate<Strategy> prevPosition =
mostBackwardCaretPosition(positions, CanCrossEditingBoundary);
if (positions.atLastEditingPositionForNode() && prevPosition.isNotNull() &&
!hasEditableStyle(*prevPosition.anchorNode()))
return true;
return nextPosition.isNotNull() &&
!hasEditableStyle(*nextPosition.anchorNode()) &&
prevPosition.isNotNull() &&
!hasEditableStyle(*prevPosition.anchorNode());
}
template <typename Strategy>
static bool isVisuallyEquivalentCandidateAlgorithm(
const PositionTemplate<Strategy>& position) {
Node* const anchorNode = position.anchorNode();
if (!anchorNode)
return false;
LayoutObject* layoutObject = anchorNode->layoutObject();
if (!layoutObject)
return false;
if (layoutObject->style()->visibility() != EVisibility::Visible)
return false;
if (layoutObject->isBR()) {
// TODO(leviw) The condition should be
// m_anchorType == PositionAnchorType::BeforeAnchor, but for now we
// still need to support legacy positions.
if (position.isAfterAnchor())
return false;
if (position.computeEditingOffset())
return false;
const Node* parent = Strategy::parent(*anchorNode);
return parent->layoutObject() && parent->layoutObject()->isSelectable();
}
if (layoutObject->isText())
return layoutObject->isSelectable() && inRenderedText(position);
if (layoutObject->isSVG()) {
// We don't consider SVG elements are contenteditable except for
// associated |layoutObject| returns |isText()| true,
// e.g. |LayoutSVGInlineText|.
return false;
}
if (isDisplayInsideTable(anchorNode) || editingIgnoresContent(*anchorNode)) {
if (!position.atFirstEditingPositionForNode() &&
!position.atLastEditingPositionForNode())
return false;
const Node* parent = Strategy::parent(*anchorNode);
return parent->layoutObject() && parent->layoutObject()->isSelectable();
}
if (anchorNode->document().documentElement() == anchorNode ||
anchorNode->isDocumentNode())
return false;
if (!layoutObject->isSelectable())
return false;
if (layoutObject->isLayoutBlockFlow() || layoutObject->isFlexibleBox() ||
layoutObject->isLayoutGrid()) {
if (toLayoutBlock(layoutObject)->logicalHeight() ||
isHTMLBodyElement(*anchorNode)) {
if (!hasRenderedNonAnonymousDescendantsWithHeight(layoutObject))
return position.atFirstEditingPositionForNode();
return hasEditableStyle(*anchorNode) && atEditingBoundary(position);
}
} else {
return hasEditableStyle(*anchorNode) && atEditingBoundary(position);
}
return false;
}
bool isVisuallyEquivalentCandidate(const Position& position) {
return isVisuallyEquivalentCandidateAlgorithm<EditingStrategy>(position);
}
bool isVisuallyEquivalentCandidate(const PositionInFlatTree& position) {
return isVisuallyEquivalentCandidateAlgorithm<EditingInFlatTreeStrategy>(
position);
}
template <typename Strategy>
static IntRect absoluteCaretBoundsOfAlgorithm(
const VisiblePositionTemplate<Strategy>& visiblePosition) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
LayoutObject* layoutObject;
LayoutRect localRect = localCaretRectOfPosition(
visiblePosition.toPositionWithAffinity(), layoutObject);
if (localRect.isEmpty() || !layoutObject)
return IntRect();
return layoutObject->localToAbsoluteQuad(FloatRect(localRect))
.enclosingBoundingBox();
}
IntRect absoluteCaretBoundsOf(const VisiblePosition& visiblePosition) {
return absoluteCaretBoundsOfAlgorithm<EditingStrategy>(visiblePosition);
}
IntRect absoluteCaretBoundsOf(
const VisiblePositionInFlatTree& visiblePosition) {
return absoluteCaretBoundsOfAlgorithm<EditingInFlatTreeStrategy>(
visiblePosition);
}
template <typename Strategy>
static VisiblePositionTemplate<Strategy> skipToEndOfEditingBoundary(
const VisiblePositionTemplate<Strategy>& pos,
const PositionTemplate<Strategy>& anchor) {
DCHECK(pos.isValid()) << pos;
if (pos.isNull())
return pos;
ContainerNode* highestRoot = highestEditableRoot(anchor);
ContainerNode* highestRootOfPos = highestEditableRoot(pos.deepEquivalent());
// Return |pos| itself if the two are from the very same editable region,
// or both are non-editable.
if (highestRootOfPos == highestRoot)
return pos;
// If this is not editable but |pos| has an editable root, skip to the end
if (!highestRoot && highestRootOfPos)
return createVisiblePosition(
PositionTemplate<Strategy>(highestRootOfPos,
PositionAnchorType::AfterAnchor)
.parentAnchoredEquivalent());
// That must mean that |pos| is not editable. Return the next position after
// |pos| that is in the same editable region as this position
DCHECK(highestRoot);
return firstEditableVisiblePositionAfterPositionInRoot(pos.deepEquivalent(),
*highestRoot);
}
template <typename Strategy>
static UChar32 characterAfterAlgorithm(
const VisiblePositionTemplate<Strategy>& visiblePosition) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
// We canonicalize to the first of two equivalent candidates, but the second
// of the two candidates is the one that will be inside the text node
// containing the character after this visible position.
const PositionTemplate<Strategy> pos =
mostForwardCaretPosition(visiblePosition.deepEquivalent());
if (!pos.isOffsetInAnchor())
return 0;
Node* containerNode = pos.computeContainerNode();
if (!containerNode || !containerNode->isTextNode())
return 0;
unsigned offset = static_cast<unsigned>(pos.offsetInContainerNode());
Text* textNode = toText(containerNode);
unsigned length = textNode->length();
if (offset >= length)
return 0;
return textNode->data().characterStartingAt(offset);
}
UChar32 characterAfter(const VisiblePosition& visiblePosition) {
return characterAfterAlgorithm<EditingStrategy>(visiblePosition);
}
UChar32 characterAfter(const VisiblePositionInFlatTree& visiblePosition) {
return characterAfterAlgorithm<EditingInFlatTreeStrategy>(visiblePosition);
}
template <typename Strategy>
static UChar32 characterBeforeAlgorithm(
const VisiblePositionTemplate<Strategy>& visiblePosition) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
return characterAfter(previousPositionOf(visiblePosition));
}
UChar32 characterBefore(const VisiblePosition& visiblePosition) {
return characterBeforeAlgorithm<EditingStrategy>(visiblePosition);
}
UChar32 characterBefore(const VisiblePositionInFlatTree& visiblePosition) {
return characterBeforeAlgorithm<EditingInFlatTreeStrategy>(visiblePosition);
}
template <typename Strategy>
static PositionTemplate<Strategy> leftVisuallyDistinctCandidate(
const VisiblePositionTemplate<Strategy>& visiblePosition) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
const PositionTemplate<Strategy> deepPosition =
visiblePosition.deepEquivalent();
PositionTemplate<Strategy> p = deepPosition;
if (p.isNull())
return PositionTemplate<Strategy>();
const PositionTemplate<Strategy> downstreamStart =
mostForwardCaretPosition(p);
TextDirection primaryDirection = primaryDirectionOf(*p.anchorNode());
const TextAffinity affinity = visiblePosition.affinity();
while (true) {
InlineBoxPosition boxPosition =
computeInlineBoxPosition(p, affinity, primaryDirection);
InlineBox* box = boxPosition.inlineBox;
int offset = boxPosition.offsetInBox;
if (!box)
return primaryDirection == TextDirection::Ltr
? previousVisuallyDistinctCandidate(deepPosition)
: nextVisuallyDistinctCandidate(deepPosition);
LineLayoutItem lineLayoutItem = box->getLineLayoutItem();
while (true) {
if ((lineLayoutItem.isAtomicInlineLevel() || lineLayoutItem.isBR()) &&
offset == box->caretRightmostOffset())
return box->isLeftToRightDirection()
? previousVisuallyDistinctCandidate(deepPosition)
: nextVisuallyDistinctCandidate(deepPosition);
if (!lineLayoutItem.node()) {
box = box->prevLeafChild();
if (!box)
return primaryDirection == TextDirection::Ltr
? previousVisuallyDistinctCandidate(deepPosition)
: nextVisuallyDistinctCandidate(deepPosition);
lineLayoutItem = box->getLineLayoutItem();
offset = box->caretRightmostOffset();
continue;
}
offset = box->isLeftToRightDirection()
? previousGraphemeBoundaryOf(lineLayoutItem.node(), offset)
: nextGraphemeBoundaryOf(lineLayoutItem.node(), offset);
int caretMinOffset = box->caretMinOffset();
int caretMaxOffset = box->caretMaxOffset();
if (offset > caretMinOffset && offset < caretMaxOffset)
break;
if (box->isLeftToRightDirection() ? offset < caretMinOffset
: offset > caretMaxOffset) {
// Overshot to the left.
InlineBox* prevBox = box->prevLeafChildIgnoringLineBreak();
if (!prevBox) {
PositionTemplate<Strategy> positionOnLeft =
primaryDirection == TextDirection::Ltr
? previousVisuallyDistinctCandidate(
visiblePosition.deepEquivalent())
: nextVisuallyDistinctCandidate(
visiblePosition.deepEquivalent());
if (positionOnLeft.isNull())
return PositionTemplate<Strategy>();
InlineBox* boxOnLeft = computeInlineBoxPosition(
positionOnLeft, affinity, primaryDirection)
.inlineBox;
if (boxOnLeft && boxOnLeft->root() == box->root())
return PositionTemplate<Strategy>();
return positionOnLeft;
}
// Reposition at the other logical position corresponding to our
// edge's visual position and go for another round.
box = prevBox;
lineLayoutItem = box->getLineLayoutItem();
offset = prevBox->caretRightmostOffset();
continue;
}
DCHECK_EQ(offset, box->caretLeftmostOffset());
unsigned char level = box->bidiLevel();
InlineBox* prevBox = box->prevLeafChild();
if (box->direction() == primaryDirection) {
if (!prevBox) {
InlineBox* logicalStart = 0;
if (primaryDirection == TextDirection::Ltr
? box->root().getLogicalStartBoxWithNode(logicalStart)
: box->root().getLogicalEndBoxWithNode(logicalStart)) {
box = logicalStart;
lineLayoutItem = box->getLineLayoutItem();
offset = primaryDirection == TextDirection::Ltr
? box->caretMinOffset()
: box->caretMaxOffset();
}
break;
}
if (prevBox->bidiLevel() >= level)
break;
level = prevBox->bidiLevel();
InlineBox* nextBox = box;
do {
nextBox = nextBox->nextLeafChild();
} while (nextBox && nextBox->bidiLevel() > level);
if (nextBox && nextBox->bidiLevel() == level)
break;
box = prevBox;
lineLayoutItem = box->getLineLayoutItem();
offset = box->caretRightmostOffset();
if (box->direction() == primaryDirection)
break;
continue;
}
while (prevBox && !prevBox->getLineLayoutItem().node())
prevBox = prevBox->prevLeafChild();
if (prevBox) {
box = prevBox;
lineLayoutItem = box->getLineLayoutItem();
offset = box->caretRightmostOffset();
if (box->bidiLevel() > level) {
do {
prevBox = prevBox->prevLeafChild();
} while (prevBox && prevBox->bidiLevel() > level);
if (!prevBox || prevBox->bidiLevel() < level)
continue;
}
} else {
// Trailing edge of a secondary run. Set to the leading edge of
// the entire run.
while (true) {
while (InlineBox* nextBox = box->nextLeafChild()) {
if (nextBox->bidiLevel() < level)
break;
box = nextBox;
}
if (box->bidiLevel() == level)
break;
level = box->bidiLevel();
while (InlineBox* prevBox = box->prevLeafChild()) {
if (prevBox->bidiLevel() < level)
break;
box = prevBox;
}
if (box->bidiLevel() == level)
break;
level = box->bidiLevel();
}
lineLayoutItem = box->getLineLayoutItem();
offset = primaryDirection == TextDirection::Ltr ? box->caretMinOffset()
: box->caretMaxOffset();
}
break;
}
p = PositionTemplate<Strategy>::editingPositionOf(lineLayoutItem.node(),
offset);
if ((isVisuallyEquivalentCandidate(p) &&
mostForwardCaretPosition(p) != downstreamStart) ||
p.atStartOfTree() || p.atEndOfTree())
return p;
DCHECK_NE(p, deepPosition);
}
}
template <typename Strategy>
VisiblePositionTemplate<Strategy> leftPositionOfAlgorithm(
const VisiblePositionTemplate<Strategy>& visiblePosition) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
const PositionTemplate<Strategy> pos =
leftVisuallyDistinctCandidate(visiblePosition);
// TODO(yosin) Why can't we move left from the last position in a tree?
if (pos.atStartOfTree() || pos.atEndOfTree())
return VisiblePositionTemplate<Strategy>();
const VisiblePositionTemplate<Strategy> left = createVisiblePosition(pos);
DCHECK_NE(left.deepEquivalent(), visiblePosition.deepEquivalent());
return directionOfEnclosingBlock(left.deepEquivalent()) == TextDirection::Ltr
? honorEditingBoundaryAtOrBefore(left,
visiblePosition.deepEquivalent())
: honorEditingBoundaryAtOrAfter(left,
visiblePosition.deepEquivalent());
}
VisiblePosition leftPositionOf(const VisiblePosition& visiblePosition) {
return leftPositionOfAlgorithm<EditingStrategy>(visiblePosition);
}
VisiblePositionInFlatTree leftPositionOf(
const VisiblePositionInFlatTree& visiblePosition) {
return leftPositionOfAlgorithm<EditingInFlatTreeStrategy>(visiblePosition);
}
template <typename Strategy>
static PositionTemplate<Strategy> rightVisuallyDistinctCandidate(
const VisiblePositionTemplate<Strategy>& visiblePosition) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
const PositionTemplate<Strategy> deepPosition =
visiblePosition.deepEquivalent();
PositionTemplate<Strategy> p = deepPosition;
if (p.isNull())
return PositionTemplate<Strategy>();
const PositionTemplate<Strategy> downstreamStart =
mostForwardCaretPosition(p);
TextDirection primaryDirection = primaryDirectionOf(*p.anchorNode());
const TextAffinity affinity = visiblePosition.affinity();
while (true) {
InlineBoxPosition boxPosition =
computeInlineBoxPosition(p, affinity, primaryDirection);
InlineBox* box = boxPosition.inlineBox;
int offset = boxPosition.offsetInBox;
if (!box)
return primaryDirection == TextDirection::Ltr
? nextVisuallyDistinctCandidate(deepPosition)
: previousVisuallyDistinctCandidate(deepPosition);
LayoutObject* layoutObject =
LineLayoutAPIShim::layoutObjectFrom(box->getLineLayoutItem());
while (true) {
if ((layoutObject->isAtomicInlineLevel() || layoutObject->isBR()) &&
offset == box->caretLeftmostOffset())
return box->isLeftToRightDirection()
? nextVisuallyDistinctCandidate(deepPosition)
: previousVisuallyDistinctCandidate(deepPosition);
if (!layoutObject->node()) {
box = box->nextLeafChild();
if (!box)
return primaryDirection == TextDirection::Ltr
? nextVisuallyDistinctCandidate(deepPosition)
: previousVisuallyDistinctCandidate(deepPosition);
layoutObject =
LineLayoutAPIShim::layoutObjectFrom(box->getLineLayoutItem());
offset = box->caretLeftmostOffset();
continue;
}
offset = box->isLeftToRightDirection()
? nextGraphemeBoundaryOf(layoutObject->node(), offset)
: previousGraphemeBoundaryOf(layoutObject->node(), offset);
int caretMinOffset = box->caretMinOffset();
int caretMaxOffset = box->caretMaxOffset();
if (offset > caretMinOffset && offset < caretMaxOffset)
break;
if (box->isLeftToRightDirection() ? offset > caretMaxOffset
: offset < caretMinOffset) {
// Overshot to the right.
InlineBox* nextBox = box->nextLeafChildIgnoringLineBreak();
if (!nextBox) {
PositionTemplate<Strategy> positionOnRight =
primaryDirection == TextDirection::Ltr
? nextVisuallyDistinctCandidate(deepPosition)
: previousVisuallyDistinctCandidate(deepPosition);
if (positionOnRight.isNull())
return PositionTemplate<Strategy>();
InlineBox* boxOnRight =
computeInlineBoxPosition(positionOnRight, affinity,
primaryDirection)
.inlineBox;
if (boxOnRight && boxOnRight->root() == box->root())
return PositionTemplate<Strategy>();
return positionOnRight;
}
// Reposition at the other logical position corresponding to our
// edge's visual position and go for another round.
box = nextBox;
layoutObject =
LineLayoutAPIShim::layoutObjectFrom(box->getLineLayoutItem());
offset = nextBox->caretLeftmostOffset();
continue;
}
DCHECK_EQ(offset, box->caretRightmostOffset());
unsigned char level = box->bidiLevel();
InlineBox* nextBox = box->nextLeafChild();
if (box->direction() == primaryDirection) {
if (!nextBox) {
InlineBox* logicalEnd = 0;
if (primaryDirection == TextDirection::Ltr
? box->root().getLogicalEndBoxWithNode(logicalEnd)
: box->root().getLogicalStartBoxWithNode(logicalEnd)) {
box = logicalEnd;
layoutObject =
LineLayoutAPIShim::layoutObjectFrom(box->getLineLayoutItem());
offset = primaryDirection == TextDirection::Ltr
? box->caretMaxOffset()
: box->caretMinOffset();
}
break;
}
if (nextBox->bidiLevel() >= level)
break;
level = nextBox->bidiLevel();
InlineBox* prevBox = box;
do {
prevBox = prevBox->prevLeafChild();
} while (prevBox && prevBox->bidiLevel() > level);
// For example, abc FED 123 ^ CBA
if (prevBox && prevBox->bidiLevel() == level)
break;
// For example, abc 123 ^ CBA or 123 ^ CBA abc
box = nextBox;
layoutObject =
LineLayoutAPIShim::layoutObjectFrom(box->getLineLayoutItem());
offset = box->caretLeftmostOffset();
if (box->direction() == primaryDirection)
break;
continue;
}
while (nextBox && !nextBox->getLineLayoutItem().node())
nextBox = nextBox->nextLeafChild();
if (nextBox) {
box = nextBox;
layoutObject =
LineLayoutAPIShim::layoutObjectFrom(box->getLineLayoutItem());
offset = box->caretLeftmostOffset();
if (box->bidiLevel() > level) {
do {
nextBox = nextBox->nextLeafChild();
} while (nextBox && nextBox->bidiLevel() > level);
if (!nextBox || nextBox->bidiLevel() < level)
continue;
}
} else {
// Trailing edge of a secondary run. Set to the leading edge of the
// entire run.
while (true) {
while (InlineBox* prevBox = box->prevLeafChild()) {
if (prevBox->bidiLevel() < level)
break;
box = prevBox;
}
if (box->bidiLevel() == level)
break;
level = box->bidiLevel();
while (InlineBox* nextBox = box->nextLeafChild()) {
if (nextBox->bidiLevel() < level)
break;
box = nextBox;
}
if (box->bidiLevel() == level)
break;
level = box->bidiLevel();
}
layoutObject =
LineLayoutAPIShim::layoutObjectFrom(box->getLineLayoutItem());
offset = primaryDirection == TextDirection::Ltr ? box->caretMaxOffset()
: box->caretMinOffset();
}
break;
}
p = PositionTemplate<Strategy>::editingPositionOf(layoutObject->node(),
offset);
if ((isVisuallyEquivalentCandidate(p) &&
mostForwardCaretPosition(p) != downstreamStart) ||
p.atStartOfTree() || p.atEndOfTree())
return p;
DCHECK_NE(p, deepPosition);
}
}
template <typename Strategy>
static VisiblePositionTemplate<Strategy> rightPositionOfAlgorithm(
const VisiblePositionTemplate<Strategy>& visiblePosition) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
const PositionTemplate<Strategy> pos =
rightVisuallyDistinctCandidate(visiblePosition);
// FIXME: Why can't we move left from the last position in a tree?
if (pos.atStartOfTree() || pos.atEndOfTree())
return VisiblePositionTemplate<Strategy>();
const VisiblePositionTemplate<Strategy> right = createVisiblePosition(pos);
DCHECK_NE(right.deepEquivalent(), visiblePosition.deepEquivalent());
return directionOfEnclosingBlock(right.deepEquivalent()) == TextDirection::Ltr
? honorEditingBoundaryAtOrAfter(right,
visiblePosition.deepEquivalent())
: honorEditingBoundaryAtOrBefore(right,
visiblePosition.deepEquivalent());
}
VisiblePosition rightPositionOf(const VisiblePosition& visiblePosition) {
return rightPositionOfAlgorithm<EditingStrategy>(visiblePosition);
}
VisiblePositionInFlatTree rightPositionOf(
const VisiblePositionInFlatTree& visiblePosition) {
return rightPositionOfAlgorithm<EditingInFlatTreeStrategy>(visiblePosition);
}
template <typename Strategy>
static VisiblePositionTemplate<Strategy> nextPositionOfAlgorithm(
const PositionWithAffinityTemplate<Strategy>& position,
EditingBoundaryCrossingRule rule) {
const VisiblePositionTemplate<Strategy> next = createVisiblePosition(
nextVisuallyDistinctCandidate(position.position()), position.affinity());
switch (rule) {
case CanCrossEditingBoundary:
return next;
case CannotCrossEditingBoundary:
return honorEditingBoundaryAtOrAfter(next, position.position());
case CanSkipOverEditingBoundary:
return skipToEndOfEditingBoundary(next, position.position());
}
NOTREACHED();
return honorEditingBoundaryAtOrAfter(next, position.position());
}
VisiblePosition nextPositionOf(const VisiblePosition& visiblePosition,
EditingBoundaryCrossingRule rule) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
return nextPositionOfAlgorithm<EditingStrategy>(
visiblePosition.toPositionWithAffinity(), rule);
}
VisiblePositionInFlatTree nextPositionOf(
const VisiblePositionInFlatTree& visiblePosition,
EditingBoundaryCrossingRule rule) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
return nextPositionOfAlgorithm<EditingInFlatTreeStrategy>(
visiblePosition.toPositionWithAffinity(), rule);
}
template <typename Strategy>
static VisiblePositionTemplate<Strategy> skipToStartOfEditingBoundary(
const VisiblePositionTemplate<Strategy>& pos,
const PositionTemplate<Strategy>& anchor) {
DCHECK(pos.isValid()) << pos;
if (pos.isNull())
return pos;
ContainerNode* highestRoot = highestEditableRoot(anchor);
ContainerNode* highestRootOfPos = highestEditableRoot(pos.deepEquivalent());
// Return |pos| itself if the two are from the very same editable region, or
// both are non-editable.
if (highestRootOfPos == highestRoot)
return pos;
// If this is not editable but |pos| has an editable root, skip to the start
if (!highestRoot && highestRootOfPos)
return createVisiblePosition(previousVisuallyDistinctCandidate(
PositionTemplate<Strategy>(highestRootOfPos,
PositionAnchorType::BeforeAnchor)
.parentAnchoredEquivalent()));
// That must mean that |pos| is not editable. Return the last position
// before |pos| that is in the same editable region as this position
DCHECK(highestRoot);
return lastEditableVisiblePositionBeforePositionInRoot(pos.deepEquivalent(),
*highestRoot);
}
template <typename Strategy>
static VisiblePositionTemplate<Strategy> previousPositionOfAlgorithm(
const PositionTemplate<Strategy>& position,
EditingBoundaryCrossingRule rule) {
const PositionTemplate<Strategy> prevPosition =
previousVisuallyDistinctCandidate(position);
// return null visible position if there is no previous visible position
if (prevPosition.atStartOfTree())
return VisiblePositionTemplate<Strategy>();
// we should always be able to make the affinity |TextAffinity::Downstream|,
// because going previous from an |TextAffinity::Upstream| position can
// never yield another |TextAffinity::Upstream position| (unless line wrap
// length is 0!).
const VisiblePositionTemplate<Strategy> prev =
createVisiblePosition(prevPosition);
if (prev.deepEquivalent() == position)
return VisiblePositionTemplate<Strategy>();
switch (rule) {
case CanCrossEditingBoundary:
return prev;
case CannotCrossEditingBoundary:
return honorEditingBoundaryAtOrBefore(prev, position);
case CanSkipOverEditingBoundary:
return skipToStartOfEditingBoundary(prev, position);
}
NOTREACHED();
return honorEditingBoundaryAtOrBefore(prev, position);
}
VisiblePosition previousPositionOf(const VisiblePosition& visiblePosition,
EditingBoundaryCrossingRule rule) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
return previousPositionOfAlgorithm<EditingStrategy>(
visiblePosition.deepEquivalent(), rule);
}
VisiblePositionInFlatTree previousPositionOf(
const VisiblePositionInFlatTree& visiblePosition,
EditingBoundaryCrossingRule rule) {
DCHECK(visiblePosition.isValid()) << visiblePosition;
return previousPositionOfAlgorithm<EditingInFlatTreeStrategy>(
visiblePosition.deepEquivalent(), rule);
}
} // namespace blink