blob: 2e4c695b947bc5f8414b0b0d94591eecd0c47107 [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/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/EphemeralRange.h"
#include "core/editing/FrameSelection.h"
#include "core/editing/LocalCaretRect.h"
#include "core/editing/Position.h"
#include "core/editing/PositionIterator.h"
#include "core/editing/PositionWithAffinity.h"
#include "core/editing/TextAffinity.h"
#include "core/editing/VisiblePosition.h"
#include "core/editing/VisibleSelection.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/forms/TextControlElement.h"
#include "core/html_names.h"
#include "core/layout/HitTestRequest.h"
#include "core/layout/HitTestResult.h"
#include "core/layout/LayoutInline.h"
#include "core/layout/LayoutTextFragment.h"
#include "core/layout/LayoutView.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"
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& position) {
// 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");
// 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.GetDocument());
DCHECK(!position.GetDocument()->NeedsLayoutTreeUpdate());
const PositionType& backward_candidate = MostBackwardCaretPosition(position);
if (IsVisuallyEquivalentCandidate(backward_candidate))
return backward_candidate;
const PositionType& forward_candidate = MostForwardCaretPosition(position);
if (IsVisuallyEquivalentCandidate(forward_candidate))
return forward_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.
const PositionType& next = CanonicalizeCandidate(NextCandidate(position));
const PositionType& prev = CanonicalizeCandidate(PreviousCandidate(position));
// 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.
Node* const node = position.ComputeContainerNode();
if (node && node->GetDocument().documentElement() == node &&
!HasEditableStyle(*node) && node->GetDocument().body() &&
HasEditableStyle(*node->GetDocument().body()))
return next.IsNotNull() ? next : prev;
Element* const editing_root = 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 ((editing_root &&
editing_root->GetDocument().documentElement() == editing_root) ||
position.AnchorNode()->IsDocumentNode())
return next.IsNotNull() ? next : prev;
Node* const next_node = next.AnchorNode();
Node* const prev_node = prev.AnchorNode();
const bool prev_is_in_same_editable_element =
prev_node && RootEditableElementOf(prev) == editing_root;
const bool next_is_in_same_editable_element =
next_node && RootEditableElementOf(next) == editing_root;
if (prev_is_in_same_editable_element && !next_is_in_same_editable_element)
return prev;
if (next_is_in_same_editable_element && !prev_is_in_same_editable_element)
return next;
if (!next_is_in_same_editable_element && !prev_is_in_same_editable_element)
return PositionType();
// The new position should be in the same block flow element. Favor that.
Element* const original_block =
node ? EnclosingBlockFlowElement(*node) : nullptr;
const bool next_is_outside_original_block =
!next_node->IsDescendantOf(original_block) && next_node != original_block;
const bool prev_is_outside_original_block =
!prev_node->IsDescendantOf(original_block) && prev_node != original_block;
if (next_is_outside_original_block && !prev_is_outside_original_block)
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>
HonorEditingBoundaryAtOrBeforeTemplate(
const PositionWithAffinityTemplate<Strategy>& pos,
const PositionTemplate<Strategy>& anchor) {
if (pos.IsNull())
return pos;
ContainerNode* highest_root = HighestEditableRoot(anchor);
// Return empty position if |pos| is not somewhere inside the editable
// region containing this position
if (highest_root && !pos.AnchorNode()->IsDescendantOf(highest_root))
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.GetPosition()) == highest_root)
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 (!highest_root)
return PositionWithAffinityTemplate<Strategy>();
// Return the last position before |pos| that is in the same editable region
// as this position
return LastEditablePositionBeforePositionInRoot(pos.GetPosition(),
*highest_root);
}
PositionWithAffinity HonorEditingBoundaryAtOrBefore(
const PositionWithAffinity& pos,
const Position& anchor) {
return HonorEditingBoundaryAtOrBeforeTemplate(pos, anchor);
}
PositionInFlatTreeWithAffinity HonorEditingBoundaryAtOrBefore(
const PositionInFlatTreeWithAffinity& pos,
const PositionInFlatTree& anchor) {
return HonorEditingBoundaryAtOrBeforeTemplate(pos, anchor);
}
template <typename Strategy>
VisiblePositionTemplate<Strategy> HonorEditingBoundaryAtOrBeforeAlgorithm(
const VisiblePositionTemplate<Strategy>& pos,
const PositionTemplate<Strategy>& anchor) {
DCHECK(pos.IsValid()) << pos;
return CreateVisiblePosition(
HonorEditingBoundaryAtOrBefore(pos.ToPositionWithAffinity(), anchor));
}
VisiblePosition HonorEditingBoundaryAtOrBefore(
const VisiblePosition& visiblePosition,
const Position& anchor) {
return HonorEditingBoundaryAtOrBeforeAlgorithm(visiblePosition, anchor);
}
VisiblePositionInFlatTree HonorEditingBoundaryAtOrBefore(
const VisiblePositionInFlatTree& visiblePosition,
const PositionInFlatTree& anchor) {
return HonorEditingBoundaryAtOrBeforeAlgorithm(visiblePosition, anchor);
}
template <typename Strategy>
static PositionWithAffinityTemplate<Strategy>
HonorEditingBoundaryAtOrAfterTemplate(
const PositionWithAffinityTemplate<Strategy>& pos,
const PositionTemplate<Strategy>& anchor) {
if (pos.IsNull())
return pos;
ContainerNode* highest_root = HighestEditableRoot(anchor);
// Return empty position if |pos| is not somewhere inside the editable
// region containing this position
if (highest_root && !pos.AnchorNode()->IsDescendantOf(highest_root))
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.GetPosition()) == highest_root)
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 (!highest_root)
return PositionWithAffinityTemplate<Strategy>();
// Return the next position after |pos| that is in the same editable region
// as this position
return FirstEditablePositionAfterPositionInRoot(pos.GetPosition(),
*highest_root);
}
PositionWithAffinity HonorEditingBoundaryAtOrAfter(
const PositionWithAffinity& pos,
const Position& anchor) {
return HonorEditingBoundaryAtOrAfterTemplate(pos, anchor);
}
PositionInFlatTreeWithAffinity HonorEditingBoundaryAtOrAfter(
const PositionInFlatTreeWithAffinity& pos,
const PositionInFlatTree& anchor) {
return HonorEditingBoundaryAtOrAfterTemplate(pos, anchor);
}
VisiblePosition HonorEditingBoundaryAtOrAfter(const VisiblePosition& pos,
const Position& anchor) {
DCHECK(pos.IsValid()) << pos;
return CreateVisiblePosition(
HonorEditingBoundaryAtOrAfter(pos.ToPositionWithAffinity(), anchor));
}
VisiblePositionInFlatTree HonorEditingBoundaryAtOrAfter(
const VisiblePositionInFlatTree& pos,
const PositionInFlatTree& anchor) {
DCHECK(pos.IsValid()) << pos;
return CreateVisiblePosition(
HonorEditingBoundaryAtOrAfter(pos.ToPositionWithAffinity(), anchor));
}
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 anchor_node = position.AnchorNode();
if (!anchor_node)
return nullptr;
Node* document_element = anchor_node->GetDocument().documentElement();
if (!document_element)
return nullptr;
Node* boundary = position.ComputeContainerNode();
while (boundary != document_element &&
NonShadowBoundaryParentNode<Strategy>(boundary) &&
HasEditableStyle(*anchor_node) ==
HasEditableStyle(*Strategy::Parent(*boundary)))
boundary = NonShadowBoundaryParentNode<Strategy>(boundary);
return boundary;
}
template <typename Strategy>
static PositionTemplate<Strategy> PreviousBoundaryAlgorithm(
const VisiblePositionTemplate<Strategy>& c,
BoundarySearchFunction search_function) {
DCHECK(c.IsValid()) << c;
const PositionTemplate<Strategy> pos = c.DeepEquivalent();
Node* boundary = ParentEditingBoundary(pos);
if (!boundary)
return PositionTemplate<Strategy>();
const PositionTemplate<Strategy> start =
PositionTemplate<Strategy>::EditingPositionOf(boundary, 0)
.ParentAnchoredEquivalent();
const PositionTemplate<Strategy> end = pos.ParentAnchoredEquivalent();
ForwardsTextBuffer suffix_string;
if (RequiresContextForWordBoundary(CharacterBefore(c))) {
TextIteratorAlgorithm<Strategy> forwards_iterator(
end, PositionTemplate<Strategy>::AfterNode(*boundary));
while (!forwards_iterator.AtEnd()) {
forwards_iterator.CopyTextTo(&suffix_string);
int context_end_index = EndOfFirstWordBoundaryContext(
suffix_string.Data() + suffix_string.Size() -
forwards_iterator.length(),
forwards_iterator.length());
if (context_end_index < forwards_iterator.length()) {
suffix_string.Shrink(forwards_iterator.length() - context_end_index);
break;
}
forwards_iterator.Advance();
}
}
unsigned suffix_length = suffix_string.Size();
BackwardsTextBuffer string;
string.PushRange(suffix_string.Data(), suffix_string.Size());
// Treat bullets used in the text security mode as regular characters when
// looking for boundaries.
SimplifiedBackwardsTextIteratorAlgorithm<Strategy> it(
EphemeralRangeTemplate<Strategy>(start, end),
TextIteratorBehavior::Builder()
.SetEmitsSmallXForTextSecurity(true)
.Build());
unsigned next = 0;
bool need_more_context = false;
while (!it.AtEnd()) {
// iterate to get chunks until the searchFunction returns a non-zero
// value.
int run_offset = 0;
do {
run_offset += it.CopyTextTo(&string, run_offset, string.Capacity());
next = search_function(string.Data(), string.Size(),
string.Size() - suffix_length, kMayHaveMoreContext,
need_more_context);
} while (!next && run_offset < it.length());
if (next)
break;
it.Advance();
}
if (need_more_context) {
// 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.
next = search_function(string.Data(), string.Size(),
string.Size() - suffix_length, kDontHaveMoreContext,
need_more_context);
DCHECK(!need_more_context);
}
if (!next)
return it.AtEnd() ? it.StartPosition() : pos;
// Use the character iterator to translate the next value into a DOM
// position.
BackwardsCharacterIteratorAlgorithm<Strategy> char_it(
EphemeralRangeTemplate<Strategy>(start, end));
char_it.Advance(string.Size() - suffix_length - next);
// TODO(yosin) charIt can get out of shadow host.
return char_it.EndPosition();
}
template <typename Strategy>
static PositionTemplate<Strategy> NextBoundaryAlgorithm(
const VisiblePositionTemplate<Strategy>& c,
BoundarySearchFunction search_function) {
DCHECK(c.IsValid()) << c;
PositionTemplate<Strategy> pos = c.DeepEquivalent();
Node* boundary = ParentEditingBoundary(pos);
if (!boundary)
return PositionTemplate<Strategy>();
Document& d = boundary->GetDocument();
const PositionTemplate<Strategy> start(pos.ParentAnchoredEquivalent());
BackwardsTextBuffer prefix_string;
if (RequiresContextForWordBoundary(CharacterAfter(c))) {
SimplifiedBackwardsTextIteratorAlgorithm<Strategy> backwards_iterator(
EphemeralRangeTemplate<Strategy>(
PositionTemplate<Strategy>::FirstPositionInNode(d), start));
while (!backwards_iterator.AtEnd()) {
backwards_iterator.CopyTextTo(&prefix_string);
int context_start_index = StartOfLastWordBoundaryContext(
prefix_string.Data(), backwards_iterator.length());
if (context_start_index > 0) {
prefix_string.Shrink(context_start_index);
break;
}
backwards_iterator.Advance();
}
}
unsigned prefix_length = prefix_string.Size();
ForwardsTextBuffer string;
string.PushRange(prefix_string.Data(), prefix_string.Size());
const PositionTemplate<Strategy> search_start =
PositionTemplate<Strategy>::EditingPositionOf(
start.AnchorNode(), start.OffsetInContainerNode());
const PositionTemplate<Strategy> search_end =
PositionTemplate<Strategy>::LastPositionInNode(*boundary);
// Treat bullets used in the text security mode as regular characters when
// looking for boundaries
TextIteratorAlgorithm<Strategy> it(
search_start, search_end,
TextIteratorBehavior::Builder()
.SetEmitsCharactersBetweenAllVisiblePositions(true)
.SetEmitsSmallXForTextSecurity(true)
.Build());
const unsigned kInvalidOffset = static_cast<unsigned>(-1);
unsigned next = kInvalidOffset;
unsigned offset = prefix_length;
bool need_more_context = 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.
int run_offset = 0;
do {
run_offset += it.CopyTextTo(&string, run_offset, string.Capacity());
next = search_function(string.Data(), string.Size(), offset,
kMayHaveMoreContext, need_more_context);
if (!need_more_context) {
// 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() && run_offset < it.length());
if (next != string.Size())
break;
it.Advance();
}
if (need_more_context) {
// 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.
next = search_function(string.Data(), string.Size(), prefix_length,
kDontHaveMoreContext, need_more_context);
DCHECK(!need_more_context);
}
if (it.AtEnd() && next == string.Size()) {
pos = it.StartPositionInCurrentContainer();
} else if (next != kInvalidOffset && next != prefix_length) {
// Use the character iterator to translate the next value into a DOM
// position.
CharacterIteratorAlgorithm<Strategy> char_it(
search_start, search_end,
TextIteratorBehavior::Builder()
.SetEmitsCharactersBetweenAllVisiblePositions(true)
.Build());
char_it.Advance(next - prefix_length - 1);
pos = char_it.EndPosition();
if (char_it.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> vis_pos =
CreateVisiblePosition(pos);
if (vis_pos.DeepEquivalent() ==
CreateVisiblePosition(char_it.StartPosition()).DeepEquivalent()) {
char_it.Advance(1);
pos = char_it.StartPosition();
}
}
}
return pos;
}
Position NextBoundary(const VisiblePosition& visible_position,
BoundarySearchFunction search_function) {
return NextBoundaryAlgorithm(visible_position, search_function);
}
PositionInFlatTree NextBoundary(
const VisiblePositionInFlatTree& visible_position,
BoundarySearchFunction search_function) {
return NextBoundaryAlgorithm(visible_position, search_function);
}
Position PreviousBoundary(const VisiblePosition& visible_position,
BoundarySearchFunction search_function) {
return PreviousBoundaryAlgorithm(visible_position, search_function);
}
PositionInFlatTree PreviousBoundary(
const VisiblePositionInFlatTree& visible_position,
BoundarySearchFunction search_function) {
return PreviousBoundaryAlgorithm(visible_position, search_function);
}
// ---------
VisiblePosition StartOfBlock(const VisiblePosition& visible_position,
EditingBoundaryCrossingRule rule) {
DCHECK(visible_position.IsValid()) << visible_position;
Position position = visible_position.DeepEquivalent();
Element* start_block =
position.ComputeContainerNode()
? EnclosingBlock(position.ComputeContainerNode(), rule)
: nullptr;
return start_block ? VisiblePosition::FirstPositionInNode(*start_block)
: VisiblePosition();
}
VisiblePosition EndOfBlock(const VisiblePosition& visible_position,
EditingBoundaryCrossingRule rule) {
DCHECK(visible_position.IsValid()) << visible_position;
Position position = visible_position.DeepEquivalent();
Element* end_block =
position.ComputeContainerNode()
? EnclosingBlock(position.ComputeContainerNode(), rule)
: nullptr;
return end_block ? VisiblePosition::LastPositionInNode(*end_block)
: VisiblePosition();
}
bool IsStartOfBlock(const VisiblePosition& pos) {
DCHECK(pos.IsValid()) << pos;
return pos.IsNotNull() &&
pos.DeepEquivalent() ==
StartOfBlock(pos, kCanCrossEditingBoundary).DeepEquivalent();
}
bool IsEndOfBlock(const VisiblePosition& pos) {
DCHECK(pos.IsValid()) << pos;
return pos.IsNotNull() &&
pos.DeepEquivalent() ==
EndOfBlock(pos, kCanCrossEditingBoundary).DeepEquivalent();
}
// ---------
template <typename Strategy>
static VisiblePositionTemplate<Strategy> StartOfDocumentAlgorithm(
const VisiblePositionTemplate<Strategy>& visible_position) {
DCHECK(visible_position.IsValid()) << visible_position;
Node* node = visible_position.DeepEquivalent().AnchorNode();
if (!node || !node->GetDocument().documentElement())
return VisiblePositionTemplate<Strategy>();
return CreateVisiblePosition(PositionTemplate<Strategy>::FirstPositionInNode(
*node->GetDocument().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>& visible_position) {
DCHECK(visible_position.IsValid()) << visible_position;
Node* node = visible_position.DeepEquivalent().AnchorNode();
if (!node || !node->GetDocument().documentElement())
return VisiblePositionTemplate<Strategy>();
Element* doc = node->GetDocument().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, kCanCrossEditingBoundary).IsNull();
}
bool IsEndOfDocument(const VisiblePosition& p) {
DCHECK(p.IsValid()) << p;
return p.IsNotNull() && NextPositionOf(p, kCanCrossEditingBoundary).IsNull();
}
// ---------
VisiblePosition StartOfEditableContent(
const VisiblePosition& visible_position) {
DCHECK(visible_position.IsValid()) << visible_position;
ContainerNode* highest_root =
HighestEditableRoot(visible_position.DeepEquivalent());
if (!highest_root)
return VisiblePosition();
return VisiblePosition::FirstPositionInNode(*highest_root);
}
VisiblePosition EndOfEditableContent(const VisiblePosition& visible_position) {
DCHECK(visible_position.IsValid()) << visible_position;
ContainerNode* highest_root =
HighestEditableRoot(visible_position.DeepEquivalent());
if (!highest_root)
return VisiblePosition();
return VisiblePosition::LastPositionInNode(*highest_root);
}
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 next_position = NextPositionOf(position);
if (next_position.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 (!next_position.DeepEquivalent().IsAfterAnchor())
return false;
return IsTextControlElement(next_position.DeepEquivalent().AnchorNode());
}
static LayoutUnit BoundingBoxLogicalHeight(LayoutObject* o,
const LayoutRect& rect) {
return o->Style()->IsHorizontalWritingMode() ? rect.Height() : rect.Width();
}
// TODO(editing-dev): The semantics seems wrong when we're in a one-letter block
// with first-letter style, e.g., <div>F</div>, where the letter is laid-out in
// an anonymous first-letter LayoutTextFragment instead of the LayoutObject of
// the text node. It seems weird to return false in this case.
bool HasRenderedNonAnonymousDescendantsWithHeight(
const LayoutObject* layout_object) {
const LayoutObject* stop = layout_object->NextInPreOrderAfterChildren();
// TODO(editing-dev): Avoid single-character parameter names.
for (LayoutObject* o = layout_object->SlowFirstChild(); o && o != stop;
o = o->NextInPreOrder()) {
if (o->NonPseudoNode()) {
if ((o->IsText() && ToLayoutText(o)->HasNonCollapsedText()) ||
(o->IsBox() && ToLayoutBox(o)->PixelSnappedLogicalHeight()) ||
(o->IsLayoutInline() && IsEmptyInline(LineLayoutItem(o)) &&
// TODO(crbug.com/771398): Find alternative ways to check whether an
// empty LayoutInline is rendered, without checking InlineBox.
BoundingBoxLogicalHeight(o, ToLayoutInline(o)->LinesBoundingBox())))
return true;
}
}
return false;
}
VisiblePosition VisiblePositionForContentsPoint(const IntPoint& contents_point,
LocalFrame* frame) {
HitTestRequest request = HitTestRequest::kMove | HitTestRequest::kReadOnly |
HitTestRequest::kActive |
HitTestRequest::kIgnoreClipping;
HitTestResult result(request, contents_point);
frame->GetDocument()->GetLayoutView()->HitTest(result);
if (Node* node = result.InnerNode()) {
return CreateVisiblePosition(PositionRespectingEditingBoundary(
frame->Selection().ComputeVisibleSelectionInDOMTreeDeprecated().Start(),
result.LocalPoint(), node));
}
return VisiblePosition();
}
// TODO(yosin): We should use |associatedLayoutObjectOf()| in "VisibleUnits.cpp"
// where it takes |LayoutObject| from |Position|.
int CaretMinOffset(const Node* node) {
const LayoutObject* layout_object = AssociatedLayoutObjectOf(*node, 0);
return layout_object ? layout_object->CaretMinOffset() : 0;
}
int CaretMaxOffset(const Node* n) {
return EditingStrategy::CaretMaxOffset(*n);
}
template <typename Strategy>
static bool InRenderedText(const PositionTemplate<Strategy>& position) {
Node* const anchor_node = position.AnchorNode();
if (!anchor_node || !anchor_node->IsTextNode())
return false;
const int offset_in_node = position.ComputeEditingOffset();
const LayoutObject* layout_object =
AssociatedLayoutObjectOf(*anchor_node, offset_in_node);
if (!layout_object)
return false;
const LayoutText* text_layout_object = ToLayoutText(layout_object);
const int text_offset =
offset_in_node - text_layout_object->TextStartOffset();
if (!text_layout_object->ContainsCaretOffset(text_offset))
return false;
// Return false for offsets inside composed characters.
// TODO(editing-dev): Previous/NextGraphemeBoundaryOf() work on DOM offsets,
// So they should use |offset_in_node| instead of |text_offset|.
return text_offset == text_layout_object->CaretMinOffset() ||
text_offset == NextGraphemeBoundaryOf(*anchor_node,
PreviousGraphemeBoundaryOf(
*anchor_node, text_offset));
}
static FloatQuad LocalToAbsoluteQuadOf(const LocalCaretRect& caret_rect) {
return caret_rect.layout_object->LocalToAbsoluteQuad(
FloatRect(caret_rect.rect));
}
bool RendersInDifferentPosition(const Position& position1,
const Position& position2) {
if (position1.IsNull() || position2.IsNull())
return false;
const LocalCaretRect& caret_rect1 =
LocalCaretRectOfPosition(PositionWithAffinity(position1));
const LocalCaretRect& caret_rect2 =
LocalCaretRectOfPosition(PositionWithAffinity(position2));
if (!caret_rect1.layout_object || !caret_rect2.layout_object)
return caret_rect1.layout_object != caret_rect2.layout_object;
return LocalToAbsoluteQuadOf(caret_rect1) !=
LocalToAbsoluteQuadOf(caret_rect2);
}
// TODO(editing-dev): Share code with IsVisuallyEquivalentCandidate if possible.
bool EndsOfNodeAreVisuallyDistinctPositions(const Node* node) {
if (!node)
return false;
LayoutObject* layout_object = node->GetLayoutObject();
if (!layout_object)
return false;
if (!layout_object->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 layout_object->IsAtomicInlineLevel() &&
CanHaveChildrenForEditing(node) &&
!ToLayoutBox(layout_object)->Size().IsEmpty() &&
!HasRenderedNonAnonymousDescendantsWithHeight(layout_object);
}
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.GetNode())
return true;
if (IsAtomicNode(pos.GetNode()))
return true;
return pos.AtStartOfNode();
}
template <typename Strategy>
static PositionTemplate<Strategy> AdjustPositionForBackwardIteration(
const PositionTemplate<Strategy>& position) {
DCHECK(!position.IsNull());
if (!position.IsAfterAnchor())
return position;
if (IsUserSelectContain(*position.AnchorNode()))
return position.ToOffsetInAnchor();
return PositionTemplate<Strategy>::EditingPositionOf(
position.AnchorNode(), Strategy::CaretMaxOffset(*position.AnchorNode()));
}
template <typename Strategy>
static PositionTemplate<Strategy> MostBackwardCaretPosition(
const PositionTemplate<Strategy>& position,
EditingBoundaryCrossingRule rule) {
DCHECK(!NeedsLayoutTreeUpdate(position)) << position;
TRACE_EVENT0("input", "VisibleUnits::mostBackwardCaretPosition");
Node* const start_node = position.AnchorNode();
if (!start_node)
return PositionTemplate<Strategy>();
// iterate backward from there, looking for a qualified position
Node* const boundary = EnclosingVisualBoundary<Strategy>(start_node);
// FIXME: PositionIterator should respect Before and After positions.
PositionIteratorAlgorithm<Strategy> last_visible(
AdjustPositionForBackwardIteration<Strategy>(position));
const bool start_editable = HasEditableStyle(*start_node);
Node* last_node = start_node;
bool boundary_crossed = false;
for (PositionIteratorAlgorithm<Strategy> current_pos = last_visible;
!current_pos.AtStart(); current_pos.Decrement()) {
Node* current_node = current_pos.GetNode();
// Don't check for an editability change if we haven't moved to a different
// node, to avoid the expense of computing hasEditableStyle().
if (current_node != last_node) {
// Don't change editability.
const bool current_editable = HasEditableStyle(*current_node);
if (start_editable != current_editable) {
if (rule == kCannotCrossEditingBoundary)
break;
boundary_crossed = true;
}
last_node = current_node;
}
// 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(current_node) &&
current_node != boundary)
return last_visible.DeprecatedComputePosition();
// skip position in non-laid out or invisible node
const LayoutObject* const layout_object =
AssociatedLayoutObjectOf(*current_node, current_pos.OffsetInLeafNode(),
LayoutObjectSide::kFirstLetterIfOnBoundary);
if (!layout_object ||
layout_object->Style()->Visibility() != EVisibility::kVisible)
continue;
if (rule == kCanCrossEditingBoundary && boundary_crossed) {
last_visible = current_pos;
break;
}
// track last visible streamer position
if (IsStreamer<Strategy>(current_pos))
last_visible = current_pos;
// 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(current_node) &&
current_pos.AtStartOfNode())
return last_visible.DeprecatedComputePosition();
// Return position after tables and nodes which have content that can be
// ignored.
if (EditingIgnoresContent(*current_node) ||
IsDisplayInsideTable(current_node)) {
if (current_pos.AtEndOfNode())
return PositionTemplate<Strategy>::AfterNode(*current_node);
continue;
}
// return current position if it is in laid out text
if (!layout_object->IsText())
continue;
const LayoutText* const text_layout_object = ToLayoutText(layout_object);
if (!text_layout_object->HasNonCollapsedText())
continue;
const unsigned text_start_offset = text_layout_object->TextStartOffset();
if (current_node != start_node) {
// 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>(
current_node, layout_object->CaretMaxOffset() + text_start_offset);
}
DCHECK_GE(current_pos.OffsetInLeafNode(),
static_cast<int>(text_layout_object->TextStartOffset()));
if (text_layout_object->IsAfterNonCollapsedCharacter(
current_pos.OffsetInLeafNode() -
text_layout_object->TextStartOffset()))
return current_pos.ComputePosition();
}
return last_visible.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) {
DCHECK(!NeedsLayoutTreeUpdate(position)) << position;
TRACE_EVENT0("input", "VisibleUnits::mostForwardCaretPosition");
Node* const start_node = position.AnchorNode();
if (!start_node)
return PositionTemplate<Strategy>();
// iterate forward from there, looking for a qualified position
Node* const boundary = EnclosingVisualBoundary<Strategy>(start_node);
// FIXME: PositionIterator should respect Before and After positions.
PositionIteratorAlgorithm<Strategy> last_visible(
position.IsAfterAnchor()
? PositionTemplate<Strategy>::EditingPositionOf(
position.AnchorNode(),
Strategy::CaretMaxOffset(*position.AnchorNode()))
: position);
const bool start_editable = HasEditableStyle(*start_node);
Node* last_node = start_node;
bool boundary_crossed = false;
for (PositionIteratorAlgorithm<Strategy> current_pos = last_visible;
!current_pos.AtEnd(); current_pos.Increment()) {
Node* current_node = current_pos.GetNode();
// Don't check for an editability change if we haven't moved to a different
// node, to avoid the expense of computing hasEditableStyle().
if (current_node != last_node) {
// Don't change editability.
const bool current_editable = HasEditableStyle(*current_node);
if (start_editable != current_editable) {
if (rule == kCannotCrossEditingBoundary)
break;
boundary_crossed = true;
}
last_node = current_node;
}
// stop before going above the body, up into the head
// return the last visible streamer position
if (IsHTMLBodyElement(*current_node) && current_pos.AtEndOfNode())
break;
// Do not move to a visually distinct position.
if (EndsOfNodeAreVisuallyDistinctPositions(current_node) &&
current_node != boundary)
return last_visible.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) == current_node)
return last_visible.DeprecatedComputePosition();
// skip position in non-laid out or invisible node
const LayoutObject* const layout_object =
AssociatedLayoutObjectOf(*current_node, current_pos.OffsetInLeafNode());
if (!layout_object ||
layout_object->Style()->Visibility() != EVisibility::kVisible)
continue;
if (rule == kCanCrossEditingBoundary && boundary_crossed)
return current_pos.DeprecatedComputePosition();
// track last visible streamer position
if (IsStreamer<Strategy>(current_pos))
last_visible = current_pos;
// Return position before tables and nodes which have content that can be
// ignored.
if (EditingIgnoresContent(*current_node) ||
IsDisplayInsideTable(current_node)) {
if (current_pos.OffsetInLeafNode() <= layout_object->CaretMinOffset())
return PositionTemplate<Strategy>::EditingPositionOf(
current_node, layout_object->CaretMinOffset());
continue;
}
// return current position if it is in laid out text
if (!layout_object->IsText())
continue;
const LayoutText* const text_layout_object = ToLayoutText(layout_object);
if (!text_layout_object->HasNonCollapsedText())
continue;
const unsigned text_start_offset = text_layout_object->TextStartOffset();
if (current_node != start_node) {
DCHECK(current_pos.AtStartOfNode());
return PositionTemplate<Strategy>(
current_node, layout_object->CaretMinOffset() + text_start_offset);
}
DCHECK_GE(current_pos.OffsetInLeafNode(),
static_cast<int>(text_layout_object->TextStartOffset()));
if (text_layout_object->IsBeforeNonCollapsedCharacter(
current_pos.OffsetInLeafNode() -
text_layout_object->TextStartOffset()))
return current_pos.ComputePosition();
}
return last_visible.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> next_position =
MostForwardCaretPosition(positions, kCanCrossEditingBoundary);
if (positions.AtFirstEditingPositionForNode() && next_position.IsNotNull() &&
!HasEditableStyle(*next_position.AnchorNode()))
return true;
PositionTemplate<Strategy> prev_position =
MostBackwardCaretPosition(positions, kCanCrossEditingBoundary);
if (positions.AtLastEditingPositionForNode() && prev_position.IsNotNull() &&
!HasEditableStyle(*prev_position.AnchorNode()))
return true;
return next_position.IsNotNull() &&
!HasEditableStyle(*next_position.AnchorNode()) &&
prev_position.IsNotNull() &&
!HasEditableStyle(*prev_position.AnchorNode());
}
template <typename Strategy>
static bool IsVisuallyEquivalentCandidateAlgorithm(
const PositionTemplate<Strategy>& position) {
Node* const anchor_node = position.AnchorNode();
if (!anchor_node)
return false;
LayoutObject* layout_object = anchor_node->GetLayoutObject();
if (!layout_object)
return false;
if (layout_object->Style()->Visibility() != EVisibility::kVisible)
return false;
if (layout_object->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(*anchor_node);
return parent->GetLayoutObject() &&
parent->GetLayoutObject()->IsSelectable();
}
if (layout_object->IsText())
return layout_object->IsSelectable() && InRenderedText(position);
if (layout_object->IsSVG()) {
// We don't consider SVG elements are contenteditable except for
// associated |layoutObject| returns |isText()| true,
// e.g. |LayoutSVGInlineText|.
return false;
}
if (IsDisplayInsideTable(anchor_node) ||
EditingIgnoresContent(*anchor_node)) {
if (!position.AtFirstEditingPositionForNode() &&
!position.AtLastEditingPositionForNode())
return false;
const Node* parent = Strategy::Parent(*anchor_node);
return parent->GetLayoutObject() &&
parent->GetLayoutObject()->IsSelectable();
}
if (anchor_node->GetDocument().documentElement() == anchor_node ||
anchor_node->IsDocumentNode())
return false;
if (!layout_object->IsSelectable())
return false;
if (layout_object->IsLayoutBlockFlow() || layout_object->IsFlexibleBox() ||
layout_object->IsLayoutGrid()) {
if (ToLayoutBlock(layout_object)->LogicalHeight() ||
IsHTMLBodyElement(*anchor_node)) {
if (!HasRenderedNonAnonymousDescendantsWithHeight(layout_object))
return position.AtFirstEditingPositionForNode();
return HasEditableStyle(*anchor_node) && AtEditingBoundary(position);
}
} else {
return HasEditableStyle(*anchor_node) && 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>& visible_position) {
DCHECK(visible_position.IsValid()) << visible_position;
const LocalCaretRect& caret_rect =
LocalCaretRectOfPosition(visible_position.ToPositionWithAffinity());
if (caret_rect.IsEmpty())
return IntRect();
return LocalToAbsoluteQuadOf(caret_rect).EnclosingBoundingBox();
}
IntRect AbsoluteCaretBoundsOf(const VisiblePosition& visible_position) {
return AbsoluteCaretBoundsOfAlgorithm<EditingStrategy>(visible_position);
}
template <typename Strategy>
static IntRect AbsoluteSelectionBoundsOfAlgorithm(
const VisiblePositionTemplate<Strategy>& visible_position) {
DCHECK(visible_position.IsValid()) << visible_position;
const LocalCaretRect& caret_rect =
LocalSelectionRectOfPosition(visible_position.ToPositionWithAffinity());
if (caret_rect.IsEmpty())
return IntRect();
return LocalToAbsoluteQuadOf(caret_rect).EnclosingBoundingBox();
}
IntRect AbsoluteSelectionBoundsOf(const VisiblePosition& visible_position) {
return AbsoluteSelectionBoundsOfAlgorithm<EditingStrategy>(visible_position);
}
IntRect AbsoluteCaretBoundsOf(
const VisiblePositionInFlatTree& visible_position) {
return AbsoluteCaretBoundsOfAlgorithm<EditingInFlatTreeStrategy>(
visible_position);
}
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* highest_root = HighestEditableRoot(anchor);
ContainerNode* highest_root_of_pos =
HighestEditableRoot(pos.DeepEquivalent());
// Return |pos| itself if the two are from the very same editable region,
// or both are non-editable.
if (highest_root_of_pos == highest_root)
return pos;
// If this is not editable but |pos| has an editable root, skip to the end
if (!highest_root && highest_root_of_pos)
return CreateVisiblePosition(
PositionTemplate<Strategy>(highest_root_of_pos,
PositionAnchorType::kAfterAnchor)
.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(highest_root);
return FirstEditableVisiblePositionAfterPositionInRoot(pos.DeepEquivalent(),
*highest_root);
}
template <typename Strategy>
static UChar32 CharacterAfterAlgorithm(
const VisiblePositionTemplate<Strategy>& visible_position) {
DCHECK(visible_position.IsValid()) << visible_position;
// 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(visible_position.DeepEquivalent());
if (!pos.IsOffsetInAnchor())
return 0;
Node* container_node = pos.ComputeContainerNode();
if (!container_node || !container_node->IsTextNode())
return 0;
unsigned offset = static_cast<unsigned>(pos.OffsetInContainerNode());
Text* text_node = ToText(container_node);
unsigned length = text_node->length();
if (offset >= length)
return 0;
return text_node->data().CharacterStartingAt(offset);
}
UChar32 CharacterAfter(const VisiblePosition& visible_position) {
return CharacterAfterAlgorithm<EditingStrategy>(visible_position);
}
UChar32 CharacterAfter(const VisiblePositionInFlatTree& visible_position) {
return CharacterAfterAlgorithm<EditingInFlatTreeStrategy>(visible_position);
}
template <typename Strategy>
static UChar32 CharacterBeforeAlgorithm(
const VisiblePositionTemplate<Strategy>& visible_position) {
DCHECK(visible_position.IsValid()) << visible_position;
return CharacterAfter(PreviousPositionOf(visible_position));
}
UChar32 CharacterBefore(const VisiblePosition& visible_position) {
return CharacterBeforeAlgorithm<EditingStrategy>(visible_position);
}
UChar32 CharacterBefore(const VisiblePositionInFlatTree& visible_position) {
return CharacterBeforeAlgorithm<EditingInFlatTreeStrategy>(visible_position);
}
template <typename Strategy>
static VisiblePositionTemplate<Strategy> NextPositionOfAlgorithm(
const PositionWithAffinityTemplate<Strategy>& position,
EditingBoundaryCrossingRule rule) {
const VisiblePositionTemplate<Strategy> next = CreateVisiblePosition(
NextVisuallyDistinctCandidate(position.GetPosition()),
position.Affinity());
switch (rule) {
case kCanCrossEditingBoundary:
return next;
case kCannotCrossEditingBoundary:
return HonorEditingBoundaryAtOrAfter(next, position.GetPosition());
case kCanSkipOverEditingBoundary:
return SkipToEndOfEditingBoundary(next, position.GetPosition());
}
NOTREACHED();
return HonorEditingBoundaryAtOrAfter(next, position.GetPosition());
}
VisiblePosition NextPositionOf(const VisiblePosition& visible_position,
EditingBoundaryCrossingRule rule) {
DCHECK(visible_position.IsValid()) << visible_position;
return NextPositionOfAlgorithm<EditingStrategy>(
visible_position.ToPositionWithAffinity(), rule);
}
VisiblePositionInFlatTree NextPositionOf(
const VisiblePositionInFlatTree& visible_position,
EditingBoundaryCrossingRule rule) {
DCHECK(visible_position.IsValid()) << visible_position;
return NextPositionOfAlgorithm<EditingInFlatTreeStrategy>(
visible_position.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* highest_root = HighestEditableRoot(anchor);
ContainerNode* highest_root_of_pos =
HighestEditableRoot(pos.DeepEquivalent());
// Return |pos| itself if the two are from the very same editable region, or
// both are non-editable.
if (highest_root_of_pos == highest_root)
return pos;
// If this is not editable but |pos| has an editable root, skip to the start
if (!highest_root && highest_root_of_pos)
return CreateVisiblePosition(PreviousVisuallyDistinctCandidate(
PositionTemplate<Strategy>(highest_root_of_pos,
PositionAnchorType::kBeforeAnchor)
.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(highest_root);
return LastEditableVisiblePositionBeforePositionInRoot(pos.DeepEquivalent(),
*highest_root);
}
template <typename Strategy>
static VisiblePositionTemplate<Strategy> PreviousPositionOfAlgorithm(
const PositionTemplate<Strategy>& position,
EditingBoundaryCrossingRule rule) {
const PositionTemplate<Strategy> prev_position =
PreviousVisuallyDistinctCandidate(position);
// return null visible position if there is no previous visible position
if (prev_position.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(prev_position);
if (prev.DeepEquivalent() == position)
return VisiblePositionTemplate<Strategy>();
switch (rule) {
case kCanCrossEditingBoundary:
return prev;
case kCannotCrossEditingBoundary:
return HonorEditingBoundaryAtOrBefore(prev, position);
case kCanSkipOverEditingBoundary:
return SkipToStartOfEditingBoundary(prev, position);
}
NOTREACHED();
return HonorEditingBoundaryAtOrBefore(prev, position);
}
VisiblePosition PreviousPositionOf(const VisiblePosition& visible_position,
EditingBoundaryCrossingRule rule) {
DCHECK(visible_position.IsValid()) << visible_position;
return PreviousPositionOfAlgorithm<EditingStrategy>(
visible_position.DeepEquivalent(), rule);
}
VisiblePositionInFlatTree PreviousPositionOf(
const VisiblePositionInFlatTree& visible_position,
EditingBoundaryCrossingRule rule) {
DCHECK(visible_position.IsValid()) << visible_position;
return PreviousPositionOfAlgorithm<EditingInFlatTreeStrategy>(
visible_position.DeepEquivalent(), rule);
}
template <typename Strategy>
static EphemeralRangeTemplate<Strategy> MakeSearchRange(
const PositionTemplate<Strategy>& pos) {
Node* node = pos.ComputeContainerNode();
if (!node)
return EphemeralRangeTemplate<Strategy>();
Document& document = node->GetDocument();
if (!document.documentElement())
return EphemeralRangeTemplate<Strategy>();
Element* boundary = EnclosingBlockFlowElement(*node);
if (!boundary)
return EphemeralRangeTemplate<Strategy>();
return EphemeralRangeTemplate<Strategy>(
pos, PositionTemplate<Strategy>::LastPositionInNode(*boundary));
}
template <typename Strategy>
static PositionTemplate<Strategy> SkipWhitespaceAlgorithm(
const PositionTemplate<Strategy>& position) {
const EphemeralRangeTemplate<Strategy>& search_range =
MakeSearchRange(position);
if (search_range.IsNull())
return position;
CharacterIteratorAlgorithm<Strategy> char_it(
search_range.StartPosition(), search_range.EndPosition(),
TextIteratorBehavior::Builder()
.SetEmitsCharactersBetweenAllVisiblePositions(true)
.Build());
PositionTemplate<Strategy> runner = position;
// TODO(editing-dev): We should consider U+20E3, COMBINING ENCLOSING KEYCAP.
// When whitespace character followed by U+20E3, we should not consider
// it as trailing white space.
for (; char_it.length(); char_it.Advance(1)) {
UChar c = char_it.CharacterAt(0);
if ((!IsSpaceOrNewline(c) && c != kNoBreakSpaceCharacter) || c == '\n')
return runner;
runner = char_it.EndPosition();
}
return runner;
}
Position SkipWhitespace(const Position& position) {
return SkipWhitespaceAlgorithm(position);
}
PositionInFlatTree SkipWhitespace(const PositionInFlatTree& position) {
return SkipWhitespaceAlgorithm(position);
}
template <typename Strategy>
static Vector<FloatQuad> ComputeTextBounds(
const EphemeralRangeTemplate<Strategy>& range) {
const PositionTemplate<Strategy>& start_position = range.StartPosition();
const PositionTemplate<Strategy>& end_position = range.EndPosition();
Node* const start_container = start_position.ComputeContainerNode();
DCHECK(start_container);
Node* const end_container = end_position.ComputeContainerNode();
DCHECK(end_container);
DCHECK(!start_container->GetDocument().NeedsLayoutTreeUpdate());
Vector<FloatQuad> result;
for (const Node& node : range.Nodes()) {
LayoutObject* const layout_object = node.GetLayoutObject();
if (!layout_object || !layout_object->IsText())
continue;
const LayoutText* layout_text = ToLayoutText(layout_object);
unsigned start_offset =
node == start_container ? start_position.OffsetInContainerNode() : 0;
unsigned end_offset = node == end_container
? end_position.OffsetInContainerNode()
: std::numeric_limits<unsigned>::max();
layout_text->AbsoluteQuadsForRange(result, start_offset, end_offset);
}
return result;
}
template <typename Strategy>
static FloatRect ComputeTextRectTemplate(
const EphemeralRangeTemplate<Strategy>& range) {
FloatRect result;
for (auto rect : ComputeTextBounds<Strategy>(range))
result.Unite(rect.BoundingBox());
return result;
}
IntRect ComputeTextRect(const EphemeralRange& range) {
return EnclosingIntRect(ComputeTextRectTemplate(range));
}
IntRect ComputeTextRect(const EphemeralRangeInFlatTree& range) {
return EnclosingIntRect(ComputeTextRectTemplate(range));
}
FloatRect ComputeTextFloatRect(const EphemeralRange& range) {
return ComputeTextRectTemplate(range);
}
} // namespace blink