blob: 4aae1a401432e24b9a3d6872023e0c35786cac86 [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/EphemeralRange.h"
#include "core/editing/SelectionAdjuster.h"
#include "core/editing/SelectionTemplate.h"
#include "core/editing/VisiblePosition.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),
base_is_first_(true),
is_directional_(false) {}
template <typename Strategy>
VisibleSelectionTemplate<Strategy>::VisibleSelectionTemplate(
const SelectionTemplate<Strategy>& selection)
: base_(selection.Base()),
extent_(selection.Extent()),
affinity_(selection.Affinity()),
base_is_first_(selection.IsBaseFirst()),
is_directional_(selection.IsDirectional()) {}
template <typename Strategy>
VisibleSelectionTemplate<Strategy> VisibleSelectionTemplate<Strategy>::Create(
const SelectionTemplate<Strategy>& selection) {
return CreateWithGranularity(selection, TextGranularity::kCharacter);
}
VisibleSelection CreateVisibleSelection(const SelectionInDOMTree& selection) {
return VisibleSelection::Create(selection);
}
VisibleSelectionInFlatTree CreateVisibleSelection(
const SelectionInFlatTree& selection) {
return VisibleSelectionInFlatTree::Create(selection);
}
// TODO(editing-dev): We should move |ComputeVisibleSelection()| to here to
// avoid forward declaration.
template <typename Strategy>
static SelectionTemplate<Strategy> ComputeVisibleSelection(
const SelectionTemplate<Strategy>&,
TextGranularity);
template <typename Strategy>
VisibleSelectionTemplate<Strategy>
VisibleSelectionTemplate<Strategy>::CreateWithGranularity(
const SelectionTemplate<Strategy>& selection,
TextGranularity granularity) {
return VisibleSelectionTemplate(
ComputeVisibleSelection(selection, granularity));
}
VisibleSelection CreateVisibleSelectionWithGranularity(
const SelectionInDOMTree& selection,
TextGranularity granularity) {
return VisibleSelection::CreateWithGranularity(selection, granularity);
}
VisibleSelectionInFlatTree CreateVisibleSelectionWithGranularity(
const SelectionInFlatTree& selection,
TextGranularity granularity) {
return VisibleSelectionInFlatTree::CreateWithGranularity(selection,
granularity);
}
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_),
affinity_(other.affinity_),
base_is_first_(other.base_is_first_),
is_directional_(other.is_directional_) {}
template <typename Strategy>
VisibleSelectionTemplate<Strategy>& VisibleSelectionTemplate<Strategy>::
operator=(const VisibleSelectionTemplate<Strategy>& other) {
base_ = other.base_;
extent_ = other.extent_;
affinity_ = other.affinity_;
base_is_first_ = other.base_is_first_;
is_directional_ = other.is_directional_;
return *this;
}
template <typename Strategy>
SelectionTemplate<Strategy> VisibleSelectionTemplate<Strategy>::AsSelection()
const {
if (base_.IsNull()) {
return typename SelectionTemplate<Strategy>::Builder()
.SetIsDirectional(is_directional_)
.Build();
}
return typename SelectionTemplate<Strategy>::Builder()
.SetBaseAndExtent(base_, extent_)
.SetAffinity(affinity_)
.SetIsDirectional(is_directional_)
.Build();
}
template <typename Strategy>
bool VisibleSelectionTemplate<Strategy>::IsCaret() const {
return base_.IsNotNull() && base_ == extent_;
}
template <typename Strategy>
bool VisibleSelectionTemplate<Strategy>::IsNone() const {
return base_.IsNull();
}
template <typename Strategy>
bool VisibleSelectionTemplate<Strategy>::IsRange() const {
return base_ != extent_;
}
template <typename Strategy>
PositionTemplate<Strategy> VisibleSelectionTemplate<Strategy>::Start() const {
return base_is_first_ ? base_ : extent_;
}
template <typename Strategy>
PositionTemplate<Strategy> VisibleSelectionTemplate<Strategy>::End() const {
return base_is_first_ ? extent_ : base_;
}
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>
static SelectionTemplate<Strategy> CanonicalizeSelection(
const SelectionTemplate<Strategy>& selection) {
if (selection.IsNone())
return SelectionTemplate<Strategy>();
const PositionTemplate<Strategy>& base =
CreateVisiblePosition(selection.Base(), selection.Affinity())
.DeepEquivalent();
if (selection.IsCaret()) {
if (base.IsNull())
return SelectionTemplate<Strategy>();
return
typename SelectionTemplate<Strategy>::Builder().Collapse(base).Build();
}
const PositionTemplate<Strategy>& extent =
CreateVisiblePosition(selection.Extent(), selection.Affinity())
.DeepEquivalent();
if (base.IsNotNull() && extent.IsNotNull()) {
return typename SelectionTemplate<Strategy>::Builder()
.SetBaseAndExtent(base, extent)
.Build();
}
if (base.IsNotNull()) {
return
typename SelectionTemplate<Strategy>::Builder().Collapse(base).Build();
}
if (extent.IsNotNull()) {
return typename SelectionTemplate<Strategy>::Builder()
.Collapse(extent)
.Build();
}
return SelectionTemplate<Strategy>();
}
template <typename Strategy>
static SelectionTemplate<Strategy> ComputeVisibleSelection(
const SelectionTemplate<Strategy>& passed_selection,
TextGranularity granularity) {
DCHECK(!NeedsLayoutTreeUpdate(passed_selection.Base()));
DCHECK(!NeedsLayoutTreeUpdate(passed_selection.Extent()));
const SelectionTemplate<Strategy>& canonicalized_selection =
CanonicalizeSelection(passed_selection);
if (canonicalized_selection.IsNone())
return SelectionTemplate<Strategy>();
const SelectionTemplate<Strategy>& granularity_adjusted_selection =
SelectionAdjuster::AdjustSelectionRespectingGranularity(
canonicalized_selection, granularity);
const SelectionTemplate<Strategy>& shadow_adjusted_selection =
SelectionAdjuster::AdjustSelectionToAvoidCrossingShadowBoundaries(
granularity_adjusted_selection);
const SelectionTemplate<Strategy>& editing_adjusted_selection =
SelectionAdjuster::AdjustSelectionToAvoidCrossingEditingBoundaries(
shadow_adjusted_selection);
const EphemeralRangeTemplate<Strategy> editing_adjusted_range(
editing_adjusted_selection.ComputeStartPosition(),
editing_adjusted_selection.ComputeEndPosition());
// TODO(editing-dev): Implement
// const SelectionTemplate<Strategy>& adjusted_selection =
// AdjustSelectionType(editing_adjusted_range);
const SelectionType selection_type =
ComputeSelectionType(editing_adjusted_range.StartPosition(),
editing_adjusted_range.EndPosition());
if (selection_type == kCaretSelection) {
return typename SelectionTemplate<Strategy>::Builder()
.Collapse(PositionWithAffinityTemplate<Strategy>(
editing_adjusted_range.StartPosition(),
passed_selection.Affinity()))
.SetIsDirectional(passed_selection.IsDirectional())
.Build();
}
DCHECK_EQ(selection_type, 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)?
const EphemeralRangeTemplate<Strategy> range(
MostForwardCaretPosition(editing_adjusted_range.StartPosition()),
MostBackwardCaretPosition(editing_adjusted_range.EndPosition()));
if (canonicalized_selection.IsBaseFirst()) {
return typename SelectionTemplate<Strategy>::Builder()
.SetIsDirectional(passed_selection.IsDirectional())
.SetAsForwardSelection(range)
.Build();
}
return typename SelectionTemplate<Strategy>::Builder()
.SetIsDirectional(passed_selection.IsDirectional())
.SetAsBackwardSelection(range)
.Build();
}
template <typename Strategy>
bool VisibleSelectionTemplate<Strategy>::IsValidFor(
const Document& document) const {
if (IsNone())
return true;
return base_.IsValidFor(document) && extent_.IsValidFor(document);
}
// 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>
VisibleSelectionTemplate<Strategy>
VisibleSelectionTemplate<Strategy>::CreateWithoutValidationDeprecated(
const PositionTemplate<Strategy>& base,
const PositionTemplate<Strategy>& extent,
TextAffinity affinity) {
DCHECK(base.IsNotNull());
DCHECK(extent.IsNotNull());
VisibleSelectionTemplate<Strategy> visible_selection;
visible_selection.base_ = base;
visible_selection.extent_ = extent;
visible_selection.base_is_first_ = base.CompareTo(extent) <= 0;
if (base == extent) {
visible_selection.affinity_ = affinity;
return visible_selection;
}
// Since |affinity_| for non-|CaretSelection| is always |kDownstream|,
// we should keep this invariant. Note: This function can be called with
// |affinity_| is |kUpstream|.
visible_selection.affinity_ = TextAffinity::kDownstream;
return visible_selection;
}
template <typename Strategy>
bool VisibleSelectionTemplate<Strategy>::IsContentEditable() const {
return IsEditablePosition(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.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>
VisiblePositionTemplate<Strategy>
VisibleSelectionTemplate<Strategy>::VisibleStart() const {
return CreateVisiblePosition(
Start(), IsRange() ? TextAffinity::kDownstream : Affinity());
}
template <typename Strategy>
VisiblePositionTemplate<Strategy>
VisibleSelectionTemplate<Strategy>::VisibleEnd() const {
return CreateVisiblePosition(
End(), IsRange() ? TextAffinity::kUpstream : Affinity());
}
template <typename Strategy>
VisiblePositionTemplate<Strategy>
VisibleSelectionTemplate<Strategy>::VisibleBase() const {
return CreateVisiblePosition(
base_, IsRange() ? (IsBaseFirst() ? TextAffinity::kUpstream
: TextAffinity::kDownstream)
: Affinity());
}
template <typename Strategy>
VisiblePositionTemplate<Strategy>
VisibleSelectionTemplate<Strategy>::VisibleExtent() const {
return CreateVisiblePosition(
extent_, IsRange() ? (IsBaseFirst() ? TextAffinity::kDownstream
: TextAffinity::kUpstream)
: Affinity());
}
template <typename Strategy>
void VisibleSelectionTemplate<Strategy>::Trace(blink::Visitor* visitor) {
visitor->Trace(base_);
visitor->Trace(extent_);
}
#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