blob: 7e51ab32566dd7f8b903b7c159a7d5cac5956968 [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/InlineBoxTraversal.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"
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) : 0;
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 VisiblePositionTemplate<Strategy> HonorEditingBoundaryAtOrAfterTemplate(
const VisiblePositionTemplate<Strategy>& pos,
const PositionTemplate<Strategy>& anchor) {
DCHECK(pos.IsValid()) << pos;
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.DeepEquivalent().AnchorNode()->IsDescendantOf(highest_root))
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()) == 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 VisiblePositionTemplate<Strategy>();
// Return the next position after |pos| that is in the same editable region
// as this position
return FirstEditableVisiblePositionAfterPositionInRoot(pos.DeepEquivalent(),
*highest_root);
}
VisiblePosition HonorEditingBoundaryAtOrAfter(const VisiblePosition& pos,
const Position& anchor) {
return HonorEditingBoundaryAtOrAfterTemplate(pos, anchor);
}
VisiblePositionInFlatTree HonorEditingBoundaryAtOrAfter(
const VisiblePositionInFlatTree& pos,
const PositionInFlatTree& anchor) {
return HonorEditingBoundaryAtOrAfterTemplate(pos, 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());
SimplifiedBackwardsTextIteratorAlgorithm<Strategy> it(
EphemeralRangeTemplate<Strategy>(start, end));
int remaining_length = 0;
unsigned next = 0;
bool need_more_context = false;
while (!it.AtEnd()) {
bool in_text_security_mode = it.IsInTextSecurityMode();
// iterate to get chunks until the searchFunction returns a non-zero
// value.
if (!in_text_security_mode) {
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) {
remaining_length = it.length() - run_offset;
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 (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;
Node* node = it.StartContainer();
int boundary_offset = remaining_length + next;
if (node->IsTextNode() && boundary_offset <= node->MaxCharacterOffset()) {
// The next variable contains a usable index into a text node
return PositionTemplate<Strategy>(node, boundary_offset);
}
// 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);
TextIteratorAlgorithm<Strategy> it(
search_start, search_end,
TextIteratorBehavior::Builder()
.SetEmitsCharactersBetweenAllVisiblePositions(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.
bool in_text_security_mode = it.IsInTextSecurityMode();
if (!in_text_security_mode) {
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;
} 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 (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)
: 0;
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)
: 0;
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 bool IsNonTextLeafChild(LayoutObject* object) {
if (object->SlowFirstChild())
return false;
if (object->IsText())
return false;
return true;
}
static InlineTextBox* SearchAheadForBetterMatch(LayoutObject* layout_object) {
LayoutBlock* container = layout_object->ContainingBlock();
for (LayoutObject* next = layout_object->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 min_offset = INT_MAX;
for (InlineTextBox* box : InlineTextBoxesOf(*ToLayoutText(next))) {
int caret_min_offset = box->CaretMinOffset();
if (caret_min_offset < min_offset) {
match = box;
min_offset = caret_min_offset;
}
}
if (match)
return match;
}
}
return 0;
}
template <typename Strategy>
PositionTemplate<Strategy> DownstreamIgnoringEditingBoundaries(
PositionTemplate<Strategy> position) {
PositionTemplate<Strategy> last_position;
while (!position.IsEquivalent(last_position)) {
last_position = position;
position = MostForwardCaretPosition(position, kCanCrossEditingBoundary);
}
return position;
}
template <typename Strategy>
PositionTemplate<Strategy> UpstreamIgnoringEditingBoundaries(
PositionTemplate<Strategy> position) {
PositionTemplate<Strategy> last_position;
while (!position.IsEquivalent(last_position)) {
last_position = position;
position = MostBackwardCaretPosition(position, kCanCrossEditingBoundary);
}
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* inline_box) {
InlineBox* prev_box = inline_box->PrevLeafChild();
if (!prev_box)
return true;
if (prev_box->Direction() == inline_box->Direction())
return true;
DCHECK_NE(prev_box->BidiLevel(), inline_box->BidiLevel());
return prev_box->BidiLevel() > inline_box->BidiLevel();
}
static InlineBoxPosition AdjustInlineBoxPositionForPrimaryDirection(
InlineBox* inline_box,
int caret_offset) {
if (caret_offset == inline_box->CaretRightmostOffset()) {
InlineBox* const next_box = inline_box->NextLeafChild();
if (!next_box || next_box->BidiLevel() >= inline_box->BidiLevel())
return InlineBoxPosition(inline_box, caret_offset);
const unsigned level = next_box->BidiLevel();
InlineBox* const prev_box =
InlineBoxTraversal::FindLeftBidiRun(*inline_box, level);
// For example, abc FED 123 ^ CBA
if (prev_box && prev_box->BidiLevel() == level)
return InlineBoxPosition(inline_box, caret_offset);
// For example, abc 123 ^ CBA
InlineBox* const result_box =
InlineBoxTraversal::FindRightBoundaryOfEntireBidiRun(*inline_box,
level);
return InlineBoxPosition(result_box, result_box->CaretRightmostOffset());
}
if (IsStartOfDifferentDirection(inline_box))
return InlineBoxPosition(inline_box, caret_offset);
const unsigned level = inline_box->PrevLeafChild()->BidiLevel();
InlineBox* const next_box =
InlineBoxTraversal::FindRightBidiRun(*inline_box, level);
if (next_box && next_box->BidiLevel() == level)
return InlineBoxPosition(inline_box, caret_offset);
InlineBox* const result_box =
InlineBoxTraversal::FindLeftBoundaryOfEntireBidiRun(*inline_box, level);
return InlineBoxPosition(result_box, result_box->CaretLeftmostOffset());
}
static InlineBoxPosition AdjustInlineBoxPositionForTextDirection(
InlineBox* inline_box,
int caret_offset,
UnicodeBidi unicode_bidi,
TextDirection primary_direction) {
if (inline_box->Direction() == primary_direction)
return AdjustInlineBoxPositionForPrimaryDirection(inline_box, caret_offset);
const unsigned char level = inline_box->BidiLevel();
if (caret_offset == inline_box->CaretLeftmostOffset()) {
InlineBox* const prev_box = inline_box->PrevLeafChildIgnoringLineBreak();
if (!prev_box || prev_box->BidiLevel() < level) {
// Left edge of a secondary run. Set to the right edge of the entire
// run.
InlineBox* const result_box =
InlineBoxTraversal::FindRightBoundaryOfEntireBidiRunIgnoringLineBreak(
*inline_box, level);
return InlineBoxPosition(result_box, result_box->CaretRightmostOffset());
}
if (prev_box->BidiLevel() <= level)
return InlineBoxPosition(inline_box, caret_offset);
// Right edge of a "tertiary" run. Set to the left edge of that run.
InlineBox* const result_box =
InlineBoxTraversal::FindLeftBoundaryOfBidiRunIgnoringLineBreak(
*inline_box, level);
return InlineBoxPosition(result_box, result_box->CaretLeftmostOffset());
}
if (unicode_bidi == UnicodeBidi::kPlaintext) {
if (inline_box->BidiLevel() < level)
return InlineBoxPosition(inline_box, inline_box->CaretLeftmostOffset());
return InlineBoxPosition(inline_box, inline_box->CaretRightmostOffset());
}
InlineBox* const next_box = inline_box->NextLeafChildIgnoringLineBreak();
if (!next_box || next_box->BidiLevel() < level) {
// Right edge of a secondary run. Set to the left edge of the entire
// run.
InlineBox* const result_box =
InlineBoxTraversal::FindLeftBoundaryOfEntireBidiRunIgnoringLineBreak(
*inline_box, level);
return InlineBoxPosition(result_box, result_box->CaretLeftmostOffset());
}
if (next_box->BidiLevel() <= level)
return InlineBoxPosition(inline_box, caret_offset);
// Left edge of a "tertiary" run. Set to the right edge of that run.
InlineBox* const result_box =
InlineBoxTraversal::FindRightBoundaryOfBidiRunIgnoringLineBreak(
*inline_box, level);
return InlineBoxPosition(result_box, result_box->CaretRightmostOffset());
}
// Returns true if |caret_offset| is at edge of |box| based on |affinity|.
// |caret_offset| must be either |box.CaretMinOffset()| or
// |box.CaretMaxOffset()|.
static bool IsCaretAtEdgeOfInlineTextBox(int caret_offset,
const InlineTextBox& box,
TextAffinity affinity) {
if (caret_offset == box.CaretMinOffset())
return affinity == TextAffinity::kDownstream;
DCHECK_EQ(caret_offset, box.CaretMaxOffset());
if (affinity == TextAffinity::kUpstream)
return true;
return box.NextLeafChild() && box.NextLeafChild()->IsLineBreak();
}
static InlineBoxPosition ComputeInlineBoxPositionForTextNode(
LayoutObject* layout_object,
int caret_offset,
TextAffinity affinity,
TextDirection primary_direction) {
InlineBox* inline_box = nullptr;
LayoutText* text_layout_object = ToLayoutText(layout_object);
InlineTextBox* candidate = nullptr;
for (InlineTextBox* box : InlineTextBoxesOf(*text_layout_object)) {
int caret_min_offset = box->CaretMinOffset();
int caret_max_offset = box->CaretMaxOffset();
if (caret_offset < caret_min_offset || caret_offset > caret_max_offset ||
(caret_offset == caret_max_offset && box->IsLineBreak()))
continue;
if (caret_offset > caret_min_offset && caret_offset < caret_max_offset)
return InlineBoxPosition(box, caret_offset);
if (IsCaretAtEdgeOfInlineTextBox(caret_offset, *box, affinity)) {
inline_box = box;
break;
}
candidate = box;
}
if (candidate && candidate == text_layout_object->LastTextBox() &&
affinity == TextAffinity::kDownstream) {
inline_box = SearchAheadForBetterMatch(text_layout_object);
if (inline_box)
caret_offset = inline_box->CaretMinOffset();
}
if (!inline_box)
inline_box = candidate;
if (!inline_box)
return InlineBoxPosition();
return AdjustInlineBoxPositionForTextDirection(
inline_box, caret_offset, layout_object->Style()->GetUnicodeBidi(),
primary_direction);
}
template <typename Strategy>
static InlineBoxPosition ComputeInlineBoxPositionTemplate(
const PositionTemplate<Strategy>& position,
TextAffinity affinity,
TextDirection primary_direction) {
int caret_offset = position.ComputeEditingOffset();
Node* const anchor_node = position.AnchorNode();
LayoutObject* layout_object =
anchor_node->IsShadowRoot()
? ToShadowRoot(anchor_node)->host().GetLayoutObject()
: anchor_node->GetLayoutObject();
DCHECK(layout_object) << position;
if (layout_object->IsText()) {
return ComputeInlineBoxPositionForTextNode(layout_object, caret_offset,
affinity, primary_direction);
}
if (CanHaveChildrenForEditing(anchor_node) &&
layout_object->IsLayoutBlockFlow() &&
HasRenderedNonAnonymousDescendantsWithHeight(layout_object)) {
// 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()|.
const PositionTemplate<Strategy>& downstream_equivalent =
DownstreamIgnoringEditingBoundaries(position);
if (downstream_equivalent != position) {
return ComputeInlineBoxPosition(
downstream_equivalent, TextAffinity::kUpstream, primary_direction);
}
const PositionTemplate<Strategy>& upstream_equivalent =
UpstreamIgnoringEditingBoundaries(position);
if (upstream_equivalent == position ||
DownstreamIgnoringEditingBoundaries(upstream_equivalent) == position)
return InlineBoxPosition();
return ComputeInlineBoxPosition(upstream_equivalent,
TextAffinity::kUpstream, primary_direction);
}
if (!layout_object->IsBox())
return InlineBoxPosition();
InlineBox* const inline_box = ToLayoutBox(layout_object)->InlineBoxWrapper();
if (!inline_box)
return InlineBoxPosition();
if ((caret_offset > inline_box->CaretMinOffset() &&
caret_offset < inline_box->CaretMaxOffset()))
return InlineBoxPosition(inline_box, caret_offset);
return AdjustInlineBoxPositionForTextDirection(
inline_box, caret_offset, layout_object->Style()->GetUnicodeBidi(),
primary_direction);
}
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 Position& position,
TextAffinity affinity,
TextDirection primary_direction) {
return ComputeInlineBoxPositionTemplate<EditingStrategy>(position, affinity,
primary_direction);
}
InlineBoxPosition ComputeInlineBoxPosition(const PositionInFlatTree& position,
TextAffinity affinity,
TextDirection primary_direction) {
return ComputeInlineBoxPositionTemplate<EditingInFlatTreeStrategy>(
position, affinity, primary_direction);
}
// TODO(editing-dev): Once we mark |LayoutObject::LocalCaretRect()| |const|,
// we should make this function to take |const LayoutObject&|.
static LocalCaretRect ComputeLocalCaretRect(
LayoutObject* layout_object,
const InlineBoxPosition box_position) {
return LocalCaretRect(
layout_object, layout_object->LocalCaretRect(box_position.inline_box,
box_position.offset_in_box));
}
template <typename Strategy>
LocalCaretRect LocalCaretRectOfPositionTemplate(
const PositionWithAffinityTemplate<Strategy>& position) {
if (position.IsNull())
return LocalCaretRect();
Node* const node = position.AnchorNode();
LayoutObject* const layout_object = node->GetLayoutObject();
if (!layout_object)
return LocalCaretRect();
const InlineBoxPosition& box_position =
ComputeInlineBoxPosition(position.GetPosition(), position.Affinity());
if (box_position.inline_box) {
return ComputeLocalCaretRect(
LineLayoutAPIShim::LayoutObjectFrom(
box_position.inline_box->GetLineLayoutItem()),
box_position);
}
// DeleteSelectionCommandTest.deleteListFromTable goes here.
return LocalCaretRect(
layout_object,
layout_object->LocalCaretRect(
nullptr, position.GetPosition().ComputeEditingOffset()));
}
// This function was added because the caret rect that is calculated by
// using the line top value instead of the selection top.
template <typename Strategy>
LocalCaretRect LocalSelectionRectOfPositionTemplate(
const PositionWithAffinityTemplate<Strategy>& position) {
if (position.IsNull())
return LocalCaretRect();
Node* const node = position.AnchorNode();
if (!node->GetLayoutObject())
return LocalCaretRect();
const InlineBoxPosition& box_position =
ComputeInlineBoxPosition(position.GetPosition(), position.Affinity());
if (!box_position.inline_box)
return LocalCaretRect();
LayoutObject* const layout_object = LineLayoutAPIShim::LayoutObjectFrom(
box_position.inline_box->GetLineLayoutItem());
const LayoutRect& rect = layout_object->LocalCaretRect(
box_position.inline_box, box_position.offset_in_box);
if (rect.IsEmpty())
return LocalCaretRect();
InlineBox* const box = box_position.inline_box;
if (layout_object->Style()->IsHorizontalWritingMode()) {
return LocalCaretRect(
layout_object,
LayoutRect(LayoutPoint(rect.X(), box->Root().SelectionTop()),
LayoutSize(rect.Width(), box->Root().SelectionHeight())));
}
return LocalCaretRect(
layout_object,
LayoutRect(LayoutPoint(box->Root().SelectionTop(), rect.Y()),
LayoutSize(box->Root().SelectionHeight(), rect.Height())));
}
LocalCaretRect LocalCaretRectOfPosition(const PositionWithAffinity& position) {
return LocalCaretRectOfPositionTemplate<EditingStrategy>(position);
}
static LocalCaretRect LocalSelectionRectOfPosition(
const PositionWithAffinity& position) {
return LocalSelectionRectOfPositionTemplate<EditingStrategy>(position);
}
LocalCaretRect LocalCaretRectOfPosition(
const PositionInFlatTreeWithAffinity& position) {
return LocalCaretRectOfPositionTemplate<EditingInFlatTreeStrategy>(position);
}
static LayoutUnit BoundingBoxLogicalHeight(LayoutObject* o,
const LayoutRect& rect) {
return o->Style()->IsHorizontalWritingMode() ? rect.Height() : rect.Width();
}
bool HasRenderedNonAnonymousDescendantsWithHeight(LayoutObject* layout_object) {
LayoutObject* stop = layout_object->NextInPreOrderAfterChildren();
for (LayoutObject* o = layout_object->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& contents_point,
LocalFrame* frame) {
HitTestRequest request = HitTestRequest::kMove | HitTestRequest::kReadOnly |
HitTestRequest::kActive |
HitTestRequest::kIgnoreClipping;
HitTestResult result(request, contents_point);
frame->GetDocument()->GetLayoutViewItem().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();
for (InlineTextBox* box : InlineTextBoxesOf(*text_layout_object)) {
if (text_offset < static_cast<int>(box->Start()) &&
!text_layout_object->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(text_offset)) {
// Return false for offsets inside composed characters.
return text_offset == text_layout_object->CaretMinOffset() ||
text_offset == NextGraphemeBoundaryOf(
anchor_node, PreviousGraphemeBoundaryOf(
anchor_node, text_offset));
}
}
return false;
}
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);
}
static bool IsVisuallyEmpty(const LayoutObject* layout) {
for (LayoutObject* child = layout->SlowFirstChild(); child;
child = child->NextSibling()) {
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->GetLayoutObject())
return false;
if (!node->GetLayoutObject()->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->GetLayoutObject()->IsAtomicInlineLevel() &&
CanHaveChildrenForEditing(node) &&
!ToLayoutBox(node->GetLayoutObject())->Size().IsEmpty() &&
IsVisuallyEmpty(node->GetLayoutObject());
}
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());
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->FirstTextBox())
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);
}
if (CanBeBackwardCaretPosition(text_layout_object,
current_pos.OffsetInLeafNode())) {
return current_pos.ComputePosition();
}
}
return last_visible.DeprecatedComputePosition();
}
// Returns true if |box| at |text_offset| can not continue on next line.
static bool CanNotContinueOnNextLine(const LayoutText& text_layout_object,
InlineBox* box,
unsigned text_offset) {
InlineTextBox* const last_text_box = text_layout_object.LastTextBox();
if (box == last_text_box)
return true;
return LineLayoutAPIShim::LayoutObjectFrom(box->GetLineLayoutItem()) ==
text_layout_object &&
ToInlineTextBox(box)->Start() >= text_offset;
}
// 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.
static bool DoesContinueOnNextLine(const LayoutText& text_layout_object,
InlineBox* box,
unsigned text_offset) {
InlineTextBox* const last_text_box = text_layout_object.LastTextBox();
DCHECK_NE(box, last_text_box);
for (InlineBox* runner = box->NextLeafChild(); runner;
runner = runner->NextLeafChild()) {
if (CanNotContinueOnNextLine(text_layout_object, runner, text_offset))
return false;
}
for (InlineBox* runner = box->PrevLeafChild(); runner;
runner = runner->PrevLeafChild()) {
if (CanNotContinueOnNextLine(text_layout_object, runner, text_offset))
return false;
}
return true;
}
// Returns true if |text_layout_object| has visible first-letter.
bool HasVisibleFirstLetter(const LayoutText& text_layout_object) {
if (!text_layout_object.IsTextFragment())
return false;
const LayoutTextFragment& layout_text_fragment =
ToLayoutTextFragment(text_layout_object);
if (!layout_text_fragment.IsRemainingTextLayoutObject())
return false;
const LayoutObject* first_letter_layout_object =
layout_text_fragment.GetFirstLetterPseudoElement()->GetLayoutObject();
if (!first_letter_layout_object)
return false;
return first_letter_layout_object->Style()->Visibility() ==
EVisibility::kVisible;
}
// TODO(editing-dev): This function is just moved out from
// |MostBackwardCaretPosition()|. We should study this function more and
// name it appropriately. See https://trac.webkit.org/changeset/32438/
// which introduce this.
static bool CanBeBackwardCaretPosition(const LayoutText* text_layout_object,
int offset_in_node) {
const unsigned text_start_offset = text_layout_object->TextStartOffset();
DCHECK_GE(offset_in_node, static_cast<int>(text_start_offset));
const unsigned text_offset = offset_in_node - text_start_offset;
InlineTextBox* const last_text_box = text_layout_object->LastTextBox();
for (InlineTextBox* box : InlineTextBoxesOf(*text_layout_object)) {
if (text_offset == box->Start()) {
if (HasVisibleFirstLetter(*text_layout_object)) {
// |offset_in_node| is at start of remaining text of
// |Text| node with :first-letter.
DCHECK_GE(offset_in_node, 1);
return true;
}
continue;
}
if (text_offset <= box->Start() + box->Len()) {
if (text_offset > box->Start())
return true;
continue;
}
if (box == last_text_box || text_offset != box->Start() + box->Len() + 1)
continue;
if (DoesContinueOnNextLine(*text_layout_object, box, text_offset + 1))
return true;
}
return false;
}
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->FirstTextBox())
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);
}
if (CanBeForwardCaretPosition(text_layout_object,
current_pos.OffsetInLeafNode())) {
return current_pos.ComputePosition();
}
}
return last_visible.DeprecatedComputePosition();
}
// TODO(editing-dev): This function is just moved out from
// |MostForwardCaretPosition()|. We should study this function more and
// name it appropriately. See https://trac.webkit.org/changeset/32438/
// which introduce this.
static bool CanBeForwardCaretPosition(const LayoutText* text_layout_object,
int offset_in_node) {
const unsigned text_start_offset = text_layout_object->TextStartOffset();
DCHECK_GE(offset_in_node, static_cast<int>(text_start_offset));
const unsigned text_offset = offset_in_node - text_start_offset;
InlineTextBox* const last_text_box = text_layout_object->LastTextBox();
for (InlineTextBox* box : InlineTextBoxesOf(*text_layout_object)) {
if (text_offset <= box->end()) {
if (text_offset >= box->Start())
return true;
continue;
}
if (box == last_text_box || text_offset != box->Start() + box->Len())
continue;
if (DoesContinueOnNextLine(*text_layout_object, box, text_offset))
return true;
}
return false;
}
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