blob: e3f650ec241b54c203497dd8219f0677e3a41f48 [file] [log] [blame]
/*
* Copyright (C) 2004, 2005, 2006 Apple Computer, 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/VisibleSelection.h"
#include "bindings/core/v8/ExceptionState.h"
#include "core/dom/Document.h"
#include "core/dom/Element.h"
#include "core/dom/Range.h"
#include "core/editing/EditingUtilities.h"
#include "core/editing/SelectionAdjuster.h"
#include "core/editing/iterators/CharacterIterator.h"
#include "platform/geometry/LayoutPoint.h"
#include "platform/wtf/Assertions.h"
#include "platform/wtf/text/CString.h"
#include "platform/wtf/text/CharacterNames.h"
#include "platform/wtf/text/StringBuilder.h"
namespace blink {
template <typename Strategy>
VisibleSelectionTemplate<Strategy>::VisibleSelectionTemplate()
: affinity_(TextAffinity::kDownstream),
selection_type_(kNoSelection),
base_is_first_(true),
is_directional_(false),
granularity_(kCharacterGranularity),
has_trailing_whitespace_(false) {}
template <typename Strategy>
VisibleSelectionTemplate<Strategy>::VisibleSelectionTemplate(
const SelectionTemplate<Strategy>& selection)
: base_(selection.Base()),
extent_(selection.Extent()),
affinity_(selection.Affinity()),
selection_type_(kNoSelection),
is_directional_(selection.IsDirectional()),
granularity_(selection.Granularity()),
has_trailing_whitespace_(selection.HasTrailingWhitespace()) {
Validate(granularity_);
}
template <typename Strategy>
VisibleSelectionTemplate<Strategy> VisibleSelectionTemplate<Strategy>::Create(
const SelectionTemplate<Strategy>& selection) {
return VisibleSelectionTemplate(selection);
}
VisibleSelection CreateVisibleSelection(const SelectionInDOMTree& selection) {
return VisibleSelection::Create(selection);
}
VisibleSelectionInFlatTree CreateVisibleSelection(
const SelectionInFlatTree& selection) {
return VisibleSelectionInFlatTree::Create(selection);
}
template <typename Strategy>
static SelectionType ComputeSelectionType(
const PositionTemplate<Strategy>& start,
const PositionTemplate<Strategy>& end) {
if (start.IsNull()) {
DCHECK(end.IsNull());
return kNoSelection;
}
DCHECK(!NeedsLayoutTreeUpdate(start)) << start << ' ' << end;
if (start == end)
return kCaretSelection;
if (MostBackwardCaretPosition(start) == MostBackwardCaretPosition(end))
return kCaretSelection;
return kRangeSelection;
}
template <typename Strategy>
VisibleSelectionTemplate<Strategy>::VisibleSelectionTemplate(
const VisibleSelectionTemplate<Strategy>& other)
: base_(other.base_),
extent_(other.extent_),
start_(other.start_),
end_(other.end_),
affinity_(other.affinity_),
selection_type_(other.selection_type_),
base_is_first_(other.base_is_first_),
is_directional_(other.is_directional_),
granularity_(other.granularity_),
has_trailing_whitespace_(other.has_trailing_whitespace_) {}
template <typename Strategy>
VisibleSelectionTemplate<Strategy>& VisibleSelectionTemplate<Strategy>::
operator=(const VisibleSelectionTemplate<Strategy>& other) {
base_ = other.base_;
extent_ = other.extent_;
start_ = other.start_;
end_ = other.end_;
affinity_ = other.affinity_;
selection_type_ = other.selection_type_;
base_is_first_ = other.base_is_first_;
is_directional_ = other.is_directional_;
granularity_ = other.granularity_;
has_trailing_whitespace_ = other.has_trailing_whitespace_;
return *this;
}
template <typename Strategy>
SelectionTemplate<Strategy> VisibleSelectionTemplate<Strategy>::AsSelection()
const {
typename SelectionTemplate<Strategy>::Builder builder;
if (base_.IsNotNull())
builder.SetBaseAndExtent(base_, extent_);
return builder.SetAffinity(affinity_)
.SetGranularity(granularity_)
.SetIsDirectional(is_directional_)
.SetHasTrailingWhitespace(has_trailing_whitespace_)
.Build();
}
EphemeralRange FirstEphemeralRangeOf(const VisibleSelection& selection) {
if (selection.IsNone())
return EphemeralRange();
Position start = selection.Start().ParentAnchoredEquivalent();
Position end = selection.end().ParentAnchoredEquivalent();
return EphemeralRange(start, end);
}
template <typename Strategy>
EphemeralRangeTemplate<Strategy>
VisibleSelectionTemplate<Strategy>::ToNormalizedEphemeralRange() const {
if (IsNone())
return EphemeralRangeTemplate<Strategy>();
// Make sure we have an updated layout since this function is called
// in the course of running edit commands which modify the DOM.
// Failing to ensure this can result in equivalentXXXPosition calls returning
// incorrect results.
DCHECK(!NeedsLayoutTreeUpdate(start_)) << *this;
if (IsCaret()) {
// If the selection is a caret, move the range start upstream. This
// helps us match the conventions of text editors tested, which make
// style determinations based on the character before the caret, if any.
const PositionTemplate<Strategy> start =
MostBackwardCaretPosition(start_).ParentAnchoredEquivalent();
return EphemeralRangeTemplate<Strategy>(start, start);
}
// If the selection is a range, select the minimum range that encompasses
// the selection. Again, this is to match the conventions of text editors
// tested, which make style determinations based on the first character of
// the selection. For instance, this operation helps to make sure that the
// "X" selected below is the only thing selected. The range should not be
// allowed to "leak" out to the end of the previous text node, or to the
// beginning of the next text node, each of which has a different style.
//
// On a treasure map, <b>X</b> marks the spot.
// ^ selected
//
DCHECK(IsRange());
return NormalizeRange(EphemeralRangeTemplate<Strategy>(start_, end_));
}
template <typename Strategy>
void VisibleSelectionTemplate<Strategy>::AppendTrailingWhitespace() {
if (IsNone())
return;
DCHECK_EQ(granularity_, kWordGranularity);
if (!IsRange())
return;
const PositionTemplate<Strategy>& new_end = SkipWhitespace(end_);
if (end_ == new_end)
return;
has_trailing_whitespace_ = true;
end_ = new_end;
}
template <typename Strategy>
void VisibleSelectionTemplate<Strategy>::SetBaseAndExtentToDeepEquivalents() {
// Move the selection to rendered positions, if possible.
bool base_and_extent_equal = base_ == extent_;
if (base_.IsNotNull()) {
base_ = CreateVisiblePosition(base_, affinity_).DeepEquivalent();
if (base_and_extent_equal)
extent_ = base_;
}
if (extent_.IsNotNull() && !base_and_extent_equal)
extent_ = CreateVisiblePosition(extent_, affinity_).DeepEquivalent();
// Make sure we do not have a dangling base or extent.
if (base_.IsNull() && extent_.IsNull()) {
base_is_first_ = true;
} else if (base_.IsNull()) {
base_ = extent_;
base_is_first_ = true;
} else if (extent_.IsNull()) {
extent_ = base_;
base_is_first_ = true;
} else {
base_is_first_ = base_.CompareTo(extent_) <= 0;
}
}
template <typename Strategy>
static PositionTemplate<Strategy> ComputeStartRespectingGranularity(
const PositionWithAffinityTemplate<Strategy>& passed_start,
TextGranularity granularity) {
DCHECK(passed_start.IsNotNull());
switch (granularity) {
case kCharacterGranularity:
// Don't do any expansion.
return passed_start.GetPosition();
case kWordGranularity: {
// General case: Select the word the caret is positioned inside of.
// If the caret is on the word boundary, select the word according to
// |wordSide|.
// Edge case: If the caret is after the last word in a soft-wrapped line
// or the last word in the document, select that last word
// (LeftWordIfOnBoundary).
// Edge case: If the caret is after the last word in a paragraph, select
// from the the end of the last word to the line break (also
// RightWordIfOnBoundary);
const VisiblePositionTemplate<Strategy> visible_start =
CreateVisiblePosition(passed_start);
if (IsEndOfEditableOrNonEditableContent(visible_start) ||
(IsEndOfLine(visible_start) && !IsStartOfLine(visible_start) &&
!IsEndOfParagraph(visible_start))) {
return StartOfWord(visible_start, kLeftWordIfOnBoundary)
.DeepEquivalent();
}
return StartOfWord(visible_start, kRightWordIfOnBoundary)
.DeepEquivalent();
}
case kSentenceGranularity:
return StartOfSentence(CreateVisiblePosition(passed_start))
.DeepEquivalent();
case kLineGranularity:
return StartOfLine(CreateVisiblePosition(passed_start)).DeepEquivalent();
case kLineBoundary:
return StartOfLine(CreateVisiblePosition(passed_start)).DeepEquivalent();
case kParagraphGranularity: {
const VisiblePositionTemplate<Strategy> pos =
CreateVisiblePosition(passed_start);
if (IsStartOfLine(pos) && IsEndOfEditableOrNonEditableContent(pos))
return StartOfParagraph(PreviousPositionOf(pos)).DeepEquivalent();
return StartOfParagraph(pos).DeepEquivalent();
}
case kDocumentBoundary:
return StartOfDocument(CreateVisiblePosition(passed_start))
.DeepEquivalent();
case kParagraphBoundary:
return StartOfParagraph(CreateVisiblePosition(passed_start))
.DeepEquivalent();
case kSentenceBoundary:
return StartOfSentence(CreateVisiblePosition(passed_start))
.DeepEquivalent();
}
NOTREACHED();
return passed_start.GetPosition();
}
template <typename Strategy>
static PositionTemplate<Strategy> ComputeEndRespectingGranularity(
const PositionTemplate<Strategy>& start,
const PositionWithAffinityTemplate<Strategy>& passed_end,
TextGranularity granularity) {
DCHECK(passed_end.IsNotNull());
switch (granularity) {
case kCharacterGranularity:
// Don't do any expansion.
return passed_end.GetPosition();
case kWordGranularity: {
// General case: Select the word the caret is positioned inside of.
// If the caret is on the word boundary, select the word according to
// |wordSide|.
// Edge case: If the caret is after the last word in a soft-wrapped line
// or the last word in the document, select that last word
// (|LeftWordIfOnBoundary|).
// Edge case: If the caret is after the last word in a paragraph, select
// from the the end of the last word to the line break (also
// |RightWordIfOnBoundary|);
const VisiblePositionTemplate<Strategy> original_end =
CreateVisiblePosition(passed_end);
EWordSide side = kRightWordIfOnBoundary;
if (IsEndOfEditableOrNonEditableContent(original_end) ||
(IsEndOfLine(original_end) && !IsStartOfLine(original_end) &&
!IsEndOfParagraph(original_end)))
side = kLeftWordIfOnBoundary;
const VisiblePositionTemplate<Strategy> word_end =
EndOfWord(original_end, side);
if (!IsEndOfParagraph(original_end))
return word_end.DeepEquivalent();
if (IsEmptyTableCell(start.AnchorNode()))
return word_end.DeepEquivalent();
// Select the paragraph break (the space from the end of a paragraph
// to the start of the next one) to match TextEdit.
const VisiblePositionTemplate<Strategy> end = NextPositionOf(word_end);
Element* const table = TableElementJustBefore(end);
if (!table) {
if (end.IsNull())
return word_end.DeepEquivalent();
return end.DeepEquivalent();
}
if (!IsEnclosingBlock(table))
return word_end.DeepEquivalent();
// The paragraph break after the last paragraph in the last cell
// of a block table ends at the start of the paragraph after the
// table.
const VisiblePositionTemplate<Strategy> next =
NextPositionOf(end, kCannotCrossEditingBoundary);
if (next.IsNull())
return word_end.DeepEquivalent();
return next.DeepEquivalent();
}
case kSentenceGranularity:
return EndOfSentence(CreateVisiblePosition(passed_end)).DeepEquivalent();
case kLineGranularity: {
const VisiblePositionTemplate<Strategy> end =
EndOfLine(CreateVisiblePosition(passed_end));
if (!IsEndOfParagraph(end))
return end.DeepEquivalent();
// If the end of this line is at the end of a paragraph, include the
// space after the end of the line in the selection.
const VisiblePositionTemplate<Strategy> next = NextPositionOf(end);
if (next.IsNull())
return end.DeepEquivalent();
return next.DeepEquivalent();
}
case kLineBoundary:
return EndOfLine(CreateVisiblePosition(passed_end)).DeepEquivalent();
case kParagraphGranularity: {
const VisiblePositionTemplate<Strategy> visible_paragraph_end =
EndOfParagraph(CreateVisiblePosition(passed_end));
// Include the "paragraph break" (the space from the end of this
// paragraph to the start of the next one) in the selection.
const VisiblePositionTemplate<Strategy> end =
NextPositionOf(visible_paragraph_end);
Element* const table = TableElementJustBefore(end);
if (!table) {
if (end.IsNull())
return visible_paragraph_end.DeepEquivalent();
return end.DeepEquivalent();
}
if (!IsEnclosingBlock(table)) {
// There is no paragraph break after the last paragraph in the
// last cell of an inline table.
return visible_paragraph_end.DeepEquivalent();
}
// The paragraph break after the last paragraph in the last cell of
// a block table ends at the start of the paragraph after the table,
// not at the position just after the table.
const VisiblePositionTemplate<Strategy> next =
NextPositionOf(end, kCannotCrossEditingBoundary);
if (next.IsNull())
return visible_paragraph_end.DeepEquivalent();
return next.DeepEquivalent();
}
case kDocumentBoundary:
return EndOfDocument(CreateVisiblePosition(passed_end)).DeepEquivalent();
case kParagraphBoundary:
return EndOfParagraph(CreateVisiblePosition(passed_end)).DeepEquivalent();
case kSentenceBoundary:
return EndOfSentence(CreateVisiblePosition(passed_end)).DeepEquivalent();
}
NOTREACHED();
return passed_end.GetPosition();
}
template <typename Strategy>
void VisibleSelectionTemplate<Strategy>::UpdateSelectionType() {
selection_type_ = ComputeSelectionType(start_, end_);
// Affinity only makes sense for a caret
if (selection_type_ != kCaretSelection)
affinity_ = TextAffinity::kDownstream;
}
template <typename Strategy>
void VisibleSelectionTemplate<Strategy>::Validate(TextGranularity granularity) {
DCHECK(!NeedsLayoutTreeUpdate(base_));
DCHECK(!NeedsLayoutTreeUpdate(extent_));
// TODO(xiaochengh): Add a DocumentLifecycle::DisallowTransitionScope here.
granularity_ = granularity;
if (granularity_ != kWordGranularity)
has_trailing_whitespace_ = false;
SetBaseAndExtentToDeepEquivalents();
if (base_.IsNull() || extent_.IsNull()) {
base_ = extent_ = start_ = end_ = PositionTemplate<Strategy>();
UpdateSelectionType();
return;
}
const PositionTemplate<Strategy> start = base_is_first_ ? base_ : extent_;
const PositionTemplate<Strategy> new_start =
ComputeStartRespectingGranularity(
PositionWithAffinityTemplate<Strategy>(start, affinity_),
granularity);
start_ = new_start.IsNotNull() ? new_start : start;
const PositionTemplate<Strategy> end = base_is_first_ ? extent_ : base_;
const PositionTemplate<Strategy> new_end = ComputeEndRespectingGranularity(
start_, PositionWithAffinityTemplate<Strategy>(end, affinity_),
granularity);
end_ = new_end.IsNotNull() ? new_end : end;
AdjustSelectionToAvoidCrossingShadowBoundaries();
AdjustSelectionToAvoidCrossingEditingBoundaries();
UpdateSelectionType();
if (GetSelectionType() == kRangeSelection) {
// "Constrain" the selection to be the smallest equivalent range of
// nodes. This is a somewhat arbitrary choice, but experience shows that
// it is useful to make to make the selection "canonical" (if only for
// purposes of comparing selections). This is an ideal point of the code
// to do this operation, since all selection changes that result in a
// RANGE come through here before anyone uses it.
// TODO(yosin) Canonicalizing is good, but haven't we already done it
// (when we set these two positions to |VisiblePosition|
// |deepEquivalent()|s above)?
start_ = MostForwardCaretPosition(start_);
end_ = MostBackwardCaretPosition(end_);
}
if (!has_trailing_whitespace_)
return;
AppendTrailingWhitespace();
}
template <typename Strategy>
bool VisibleSelectionTemplate<Strategy>::IsValidFor(
const Document& document) const {
if (IsNone())
return true;
return base_.GetDocument() == &document && !base_.IsOrphan() &&
!extent_.IsOrphan() && !start_.IsOrphan() && !end_.IsOrphan();
}
// TODO(yosin) This function breaks the invariant of this class.
// But because we use VisibleSelection to store values in editing commands for
// use when undoing the command, we need to be able to create a selection that
// while currently invalid, will be valid once the changes are undone. This is a
// design problem. To fix it we either need to change the invariants of
// |VisibleSelection| or create a new class for editing to use that can
// manipulate selections that are not currently valid.
template <typename Strategy>
void VisibleSelectionTemplate<Strategy>::SetWithoutValidation(
const PositionTemplate<Strategy>& base,
const PositionTemplate<Strategy>& extent) {
if (base.IsNull() || extent.IsNull()) {
base_ = extent_ = start_ = end_ = PositionTemplate<Strategy>();
UpdateSelectionType();
return;
}
base_ = base;
extent_ = extent;
base_is_first_ = base.CompareTo(extent) <= 0;
if (base_is_first_) {
start_ = base;
end_ = extent;
} else {
start_ = extent;
end_ = base;
}
selection_type_ = base == extent ? kCaretSelection : kRangeSelection;
if (selection_type_ != kCaretSelection) {
// Since |m_affinity| for non-|CaretSelection| is always |Downstream|,
// we should keep this invariant. Note: This function can be called with
// |m_affinity| is |TextAffinity::Upstream|.
affinity_ = TextAffinity::kDownstream;
}
}
template <typename Strategy>
void VisibleSelectionTemplate<
Strategy>::AdjustSelectionToAvoidCrossingShadowBoundaries() {
if (base_.IsNull() || start_.IsNull() || base_.IsNull())
return;
SelectionAdjuster::AdjustSelectionToAvoidCrossingShadowBoundaries(this);
}
static Element* LowestEditableAncestor(Node* node) {
while (node) {
if (HasEditableStyle(*node))
return RootEditableElement(*node);
if (isHTMLBodyElement(*node))
break;
node = node->parentNode();
}
return nullptr;
}
template <typename Strategy>
void VisibleSelectionTemplate<
Strategy>::AdjustSelectionToAvoidCrossingEditingBoundaries() {
if (base_.IsNull() || start_.IsNull() || end_.IsNull())
return;
ContainerNode* base_root = HighestEditableRoot(base_);
ContainerNode* start_root = HighestEditableRoot(start_);
ContainerNode* end_root = HighestEditableRoot(end_);
Element* base_editable_ancestor =
LowestEditableAncestor(base_.ComputeContainerNode());
// The base, start and end are all in the same region. No adjustment
// necessary.
if (base_root == start_root && base_root == end_root)
return;
// The selection is based in editable content.
if (base_root) {
// If the start is outside the base's editable root, cap it at the start of
// that root.
// If the start is in non-editable content that is inside the base's
// editable root, put it at the first editable position after start inside
// the base's editable root.
if (start_root != base_root) {
const VisiblePositionTemplate<Strategy> first =
FirstEditableVisiblePositionAfterPositionInRoot(start_, *base_root);
start_ = first.DeepEquivalent();
if (start_.IsNull()) {
NOTREACHED();
start_ = end_;
}
}
// If the end is outside the base's editable root, cap it at the end of that
// root.
// If the end is in non-editable content that is inside the base's root, put
// it at the last editable position before the end inside the base's root.
if (end_root != base_root) {
const VisiblePositionTemplate<Strategy> last =
LastEditableVisiblePositionBeforePositionInRoot(end_, *base_root);
end_ = last.DeepEquivalent();
if (end_.IsNull())
end_ = start_;
}
// The selection is based in non-editable content.
} else {
// FIXME: Non-editable pieces inside editable content should be atomic, in
// the same way that editable pieces in non-editable content are atomic.
// The selection ends in editable content or non-editable content inside a
// different editable ancestor, move backward until non-editable content
// inside the same lowest editable ancestor is reached.
Element* end_editable_ancestor =
LowestEditableAncestor(end_.ComputeContainerNode());
if (end_root || end_editable_ancestor != base_editable_ancestor) {
PositionTemplate<Strategy> p = PreviousVisuallyDistinctCandidate(end_);
Element* shadow_ancestor =
end_root ? end_root->OwnerShadowHost() : nullptr;
if (p.IsNull() && shadow_ancestor)
p = PositionTemplate<Strategy>::AfterNode(shadow_ancestor);
while (p.IsNotNull() &&
!(LowestEditableAncestor(p.ComputeContainerNode()) ==
base_editable_ancestor &&
!IsEditablePosition(p))) {
Element* root = RootEditableElementOf(p);
shadow_ancestor = root ? root->OwnerShadowHost() : nullptr;
p = IsAtomicNode(p.ComputeContainerNode())
? PositionTemplate<Strategy>::InParentBeforeNode(
*p.ComputeContainerNode())
: PreviousVisuallyDistinctCandidate(p);
if (p.IsNull() && shadow_ancestor)
p = PositionTemplate<Strategy>::AfterNode(shadow_ancestor);
}
const VisiblePositionTemplate<Strategy> previous =
CreateVisiblePosition(p);
if (previous.IsNull()) {
// The selection crosses an Editing boundary. This is a
// programmer error in the editing code. Happy debugging!
NOTREACHED();
*this = VisibleSelectionTemplate<Strategy>();
return;
}
end_ = previous.DeepEquivalent();
}
// The selection starts in editable content or non-editable content inside a
// different editable ancestor, move forward until non-editable content
// inside the same lowest editable ancestor is reached.
Element* start_editable_ancestor =
LowestEditableAncestor(start_.ComputeContainerNode());
if (start_root || start_editable_ancestor != base_editable_ancestor) {
PositionTemplate<Strategy> p = NextVisuallyDistinctCandidate(start_);
Element* shadow_ancestor =
start_root ? start_root->OwnerShadowHost() : nullptr;
if (p.IsNull() && shadow_ancestor)
p = PositionTemplate<Strategy>::BeforeNode(shadow_ancestor);
while (p.IsNotNull() &&
!(LowestEditableAncestor(p.ComputeContainerNode()) ==
base_editable_ancestor &&
!IsEditablePosition(p))) {
Element* root = RootEditableElementOf(p);
shadow_ancestor = root ? root->OwnerShadowHost() : nullptr;
p = IsAtomicNode(p.ComputeContainerNode())
? PositionTemplate<Strategy>::InParentAfterNode(
*p.ComputeContainerNode())
: NextVisuallyDistinctCandidate(p);
if (p.IsNull() && shadow_ancestor)
p = PositionTemplate<Strategy>::BeforeNode(shadow_ancestor);
}
const VisiblePositionTemplate<Strategy> next = CreateVisiblePosition(p);
if (next.IsNull()) {
// The selection crosses an Editing boundary. This is a
// programmer error in the editing code. Happy debugging!
NOTREACHED();
*this = VisibleSelectionTemplate<Strategy>();
return;
}
start_ = next.DeepEquivalent();
}
}
// Correct the extent if necessary.
if (base_editable_ancestor !=
LowestEditableAncestor(extent_.ComputeContainerNode()))
extent_ = base_is_first_ ? end_ : start_;
}
template <typename Strategy>
bool VisibleSelectionTemplate<Strategy>::IsContentEditable() const {
return IsEditablePosition(Start());
}
template <typename Strategy>
bool VisibleSelectionTemplate<Strategy>::HasEditableStyle() const {
return IsEditablePosition(Start());
}
template <typename Strategy>
bool VisibleSelectionTemplate<Strategy>::IsContentRichlyEditable() const {
return IsRichlyEditablePosition(ToPositionInDOMTree(Start()));
}
template <typename Strategy>
Element* VisibleSelectionTemplate<Strategy>::RootEditableElement() const {
return RootEditableElementOf(Start());
}
template <typename Strategy>
static bool EqualSelectionsAlgorithm(
const VisibleSelectionTemplate<Strategy>& selection1,
const VisibleSelectionTemplate<Strategy>& selection2) {
if (selection1.Affinity() != selection2.Affinity() ||
selection1.IsDirectional() != selection2.IsDirectional())
return false;
if (selection1.IsNone())
return selection2.IsNone();
const VisibleSelectionTemplate<Strategy> selection_wrapper1(selection1);
const VisibleSelectionTemplate<Strategy> selection_wrapper2(selection2);
return selection_wrapper1.Start() == selection_wrapper2.Start() &&
selection_wrapper1.end() == selection_wrapper2.end() &&
selection_wrapper1.Base() == selection_wrapper2.Base() &&
selection_wrapper1.Extent() == selection_wrapper2.Extent();
}
template <typename Strategy>
bool VisibleSelectionTemplate<Strategy>::operator==(
const VisibleSelectionTemplate<Strategy>& other) const {
return EqualSelectionsAlgorithm<Strategy>(*this, other);
}
template <typename Strategy>
DEFINE_TRACE(VisibleSelectionTemplate<Strategy>) {
visitor->Trace(base_);
visitor->Trace(extent_);
visitor->Trace(start_);
visitor->Trace(end_);
}
#ifndef NDEBUG
template <typename Strategy>
void VisibleSelectionTemplate<Strategy>::ShowTreeForThis() const {
if (!Start().AnchorNode())
return;
LOG(INFO) << "\n"
<< Start()
.AnchorNode()
->ToMarkedTreeString(Start().AnchorNode(), "S",
end().AnchorNode(), "E")
.Utf8()
.data()
<< "start: " << Start().ToAnchorTypeAndOffsetString().Utf8().data()
<< "\n"
<< "end: " << end().ToAnchorTypeAndOffsetString().Utf8().data();
}
#endif
template <typename Strategy>
void VisibleSelectionTemplate<Strategy>::PrintTo(
const VisibleSelectionTemplate<Strategy>& selection,
std::ostream* ostream) {
if (selection.IsNone()) {
*ostream << "VisibleSelection()";
return;
}
*ostream << "VisibleSelection(base: " << selection.Base()
<< " extent:" << selection.Extent()
<< " start: " << selection.Start() << " end: " << selection.end()
<< ' ' << selection.Affinity() << ' '
<< (selection.IsDirectional() ? "Directional" : "NonDirectional")
<< ')';
}
template class CORE_TEMPLATE_EXPORT VisibleSelectionTemplate<EditingStrategy>;
template class CORE_TEMPLATE_EXPORT
VisibleSelectionTemplate<EditingInFlatTreeStrategy>;
std::ostream& operator<<(std::ostream& ostream,
const VisibleSelection& selection) {
VisibleSelection::PrintTo(selection, &ostream);
return ostream;
}
std::ostream& operator<<(std::ostream& ostream,
const VisibleSelectionInFlatTree& selection) {
VisibleSelectionInFlatTree::PrintTo(selection, &ostream);
return ostream;
}
} // namespace blink
#ifndef NDEBUG
void showTree(const blink::VisibleSelection& sel) {
sel.ShowTreeForThis();
}
void showTree(const blink::VisibleSelection* sel) {
if (sel)
sel->ShowTreeForThis();
}
void showTree(const blink::VisibleSelectionInFlatTree& sel) {
sel.ShowTreeForThis();
}
void showTree(const blink::VisibleSelectionInFlatTree* sel) {
if (sel)
sel->ShowTreeForThis();
}
#endif