blob: 244b48ef90bdf0db8e6cd6c5962acb19667e5bd9 [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/editing/selection_template.h"
#include <ostream>
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/editing/ephemeral_range.h"
#include "third_party/blink/renderer/core/editing/position_with_affinity.h"
namespace blink {
template <typename Strategy>
SelectionTemplate<Strategy>::SelectionTemplate(const SelectionTemplate& other)
: anchor_(other.anchor_),
focus_(other.focus_),
affinity_(other.affinity_),
direction_(other.direction_)
#if DCHECK_IS_ON()
,
dom_tree_version_(other.dom_tree_version_)
#endif
{
DCHECK(other.AssertValid());
}
template <typename Strategy>
SelectionTemplate<Strategy>::SelectionTemplate() = default;
template <typename Strategy>
bool SelectionTemplate<Strategy>::operator==(
const SelectionTemplate& other) const {
DCHECK(AssertValid());
DCHECK(other.AssertValid());
if (IsNone())
return other.IsNone();
if (other.IsNone())
return false;
DCHECK_EQ(anchor_.GetDocument(), other.GetDocument())
<< *this << ' ' << other;
return anchor_ == other.anchor_ && focus_ == other.focus_ &&
affinity_ == other.affinity_;
}
template <typename Strategy>
bool SelectionTemplate<Strategy>::operator!=(
const SelectionTemplate& other) const {
return !operator==(other);
}
template <typename Strategy>
void SelectionTemplate<Strategy>::Trace(Visitor* visitor) const {
visitor->Trace(anchor_);
visitor->Trace(focus_);
}
template <typename Strategy>
const PositionTemplate<Strategy>& SelectionTemplate<Strategy>::Anchor() const {
DCHECK(AssertValid());
DCHECK(!anchor_.IsOrphan()) << anchor_;
return anchor_;
}
template <typename Strategy>
Document* SelectionTemplate<Strategy>::GetDocument() const {
DCHECK(AssertValid());
return anchor_.GetDocument();
}
template <typename Strategy>
const PositionTemplate<Strategy>& SelectionTemplate<Strategy>::Focus() const {
DCHECK(AssertValid());
DCHECK(!focus_.IsOrphan()) << focus_;
return focus_;
}
template <typename Strategy>
bool SelectionTemplate<Strategy>::IsCaret() const {
return anchor_.IsNotNull() && anchor_ == focus_;
}
template <typename Strategy>
bool SelectionTemplate<Strategy>::IsRange() const {
return anchor_ != focus_;
}
template <typename Strategy>
bool SelectionTemplate<Strategy>::IsValidFor(const Document& document) const {
if (IsNone())
return true;
return anchor_.IsValidFor(document) && focus_.IsValidFor(document);
}
template <typename Strategy>
bool SelectionTemplate<Strategy>::AssertValidFor(
const Document& document) const {
if (!AssertValid())
return false;
if (anchor_.IsNull()) {
return true;
}
DCHECK_EQ(anchor_.GetDocument(), document) << *this;
return true;
}
#if DCHECK_IS_ON()
template <typename Strategy>
bool SelectionTemplate<Strategy>::AssertValid() const {
if (anchor_.IsNull()) {
return true;
}
DCHECK_EQ(anchor_.GetDocument()->DomTreeVersion(), dom_tree_version_)
<< *this;
DCHECK(!anchor_.IsOrphan()) << *this;
DCHECK(!focus_.IsOrphan()) << *this;
DCHECK_EQ(anchor_.GetDocument(), focus_.GetDocument());
return true;
}
#else
template <typename Strategy>
bool SelectionTemplate<Strategy>::AssertValid() const {
return true;
}
#endif
#if DCHECK_IS_ON()
template <typename Strategy>
void SelectionTemplate<Strategy>::ShowTreeForThis() const {
if (anchor_.IsNull()) {
LOG(INFO) << "\nanchor is null";
return;
}
LOG(INFO) << "\n"
<< anchor_.AnchorNode()
->ToMarkedTreeString(anchor_.AnchorNode(), "B",
focus_.AnchorNode(), "E")
.Utf8()
<< "anchor: " << anchor_.ToAnchorTypeAndOffsetString().Utf8()
<< "\n"
<< "focus: " << focus_.ToAnchorTypeAndOffsetString().Utf8();
}
#endif
template <typename Strategy>
const PositionTemplate<Strategy>&
SelectionTemplate<Strategy>::ComputeEndPosition() const {
return IsAnchorFirst() ? focus_ : anchor_;
}
template <typename Strategy>
const PositionTemplate<Strategy>&
SelectionTemplate<Strategy>::ComputeStartPosition() const {
return IsAnchorFirst() ? anchor_ : focus_;
}
template <typename Strategy>
EphemeralRangeTemplate<Strategy> SelectionTemplate<Strategy>::ComputeRange()
const {
return EphemeralRangeTemplate<Strategy>(ComputeStartPosition(),
ComputeEndPosition());
}
template <typename Strategy>
bool SelectionTemplate<Strategy>::IsAnchorFirst() const {
DCHECK(AssertValid());
if (anchor_ == focus_) {
DCHECK_EQ(direction_, Direction::kForward);
return true;
}
if (direction_ == Direction::kForward) {
DCHECK_LE(anchor_, focus_);
return true;
}
if (direction_ == Direction::kBackward) {
DCHECK_GT(anchor_, focus_);
return false;
}
// Note: Since same position can be represented in different anchor type,
// e.g. Position(div, 0) and BeforeNode(first-child), we use |<=| to check
// forward selection.
DCHECK_EQ(direction_, Direction::kNotComputed);
direction_ = anchor_ <= focus_ ? Direction::kForward : Direction::kBackward;
return direction_ == Direction::kForward;
}
template <typename Strategy>
void SelectionTemplate<Strategy>::ResetDirectionCache() const {
direction_ =
anchor_ == focus_ ? Direction::kForward : Direction::kNotComputed;
}
template <typename Strategy>
void SelectionTemplate<Strategy>::PrintTo(std::ostream* ostream,
const char* type) const {
if (IsNone()) {
*ostream << "()";
return;
}
*ostream << type << '(';
#if DCHECK_IS_ON()
if (dom_tree_version_ != anchor_.GetDocument()->DomTreeVersion()) {
*ostream << "Dirty: " << dom_tree_version_;
*ostream << " != " << anchor_.GetDocument()->DomTreeVersion() << ' ';
}
#endif
*ostream << "anchor: " << anchor_ << ", focus: " << focus_ << ')';
}
std::ostream& operator<<(std::ostream& ostream,
const SelectionInDOMTree& selection) {
selection.PrintTo(&ostream, "Selection");
return ostream;
}
std::ostream& operator<<(std::ostream& ostream,
const SelectionInFlatTree& selection) {
selection.PrintTo(&ostream, "SelectionInFlatTree");
return ostream;
}
// --
template <typename Strategy>
SelectionTemplate<Strategy>::Builder::Builder(
const SelectionTemplate<Strategy>& selection)
: selection_(selection) {}
template <typename Strategy>
SelectionTemplate<Strategy>::Builder::Builder() = default;
template <typename Strategy>
SelectionTemplate<Strategy> SelectionTemplate<Strategy>::Builder::Build()
const {
DCHECK(selection_.AssertValid());
if (selection_.direction_ == Direction::kBackward) {
DCHECK_LE(selection_.focus_, selection_.anchor_);
return selection_;
}
if (selection_.direction_ == Direction::kForward) {
if (selection_.IsNone())
return selection_;
DCHECK_LE(selection_.anchor_, selection_.focus_);
return selection_;
}
DCHECK_EQ(selection_.direction_, Direction::kNotComputed);
selection_.ResetDirectionCache();
return selection_;
}
template <typename Strategy>
typename SelectionTemplate<Strategy>::Builder&
SelectionTemplate<Strategy>::Builder::Collapse(
const PositionTemplate<Strategy>& position) {
DCHECK(position.IsConnected()) << position;
selection_.anchor_ = position;
selection_.focus_ = position;
#if DCHECK_IS_ON()
selection_.dom_tree_version_ = position.GetDocument()->DomTreeVersion();
#endif
return *this;
}
template <typename Strategy>
typename SelectionTemplate<Strategy>::Builder&
SelectionTemplate<Strategy>::Builder::Collapse(
const PositionWithAffinityTemplate<Strategy>& position_with_affinity) {
Collapse(position_with_affinity.GetPosition());
SetAffinity(position_with_affinity.Affinity());
return *this;
}
template <typename Strategy>
typename SelectionTemplate<Strategy>::Builder&
SelectionTemplate<Strategy>::Builder::Extend(
const PositionTemplate<Strategy>& position) {
DCHECK(position.IsConnected()) << position;
DCHECK_EQ(selection_.GetDocument(), position.GetDocument());
DCHECK(selection_.Anchor().IsConnected()) << selection_.Anchor();
DCHECK(selection_.AssertValid());
if (selection_.focus_.IsEquivalent(position)) {
return *this;
}
selection_.focus_ = position;
selection_.direction_ = Direction::kNotComputed;
return *this;
}
template <typename Strategy>
typename SelectionTemplate<Strategy>::Builder&
SelectionTemplate<Strategy>::Builder::SelectAllChildren(const Node& node) {
DCHECK(node.CanContainRangeEndPoint()) << node;
return SetBaseAndExtent(
EphemeralRangeTemplate<Strategy>::RangeOfContents(node));
}
template <typename Strategy>
typename SelectionTemplate<Strategy>::Builder&
SelectionTemplate<Strategy>::Builder::SetAffinity(TextAffinity affinity) {
selection_.affinity_ = affinity;
return *this;
}
template <typename Strategy>
typename SelectionTemplate<Strategy>::Builder&
SelectionTemplate<Strategy>::Builder::SetAsBackwardSelection(
const EphemeralRangeTemplate<Strategy>& range) {
DCHECK(range.IsNotNull());
DCHECK(!range.IsCollapsed());
DCHECK(selection_.IsNone()) << selection_;
selection_.anchor_ = range.EndPosition();
selection_.focus_ = range.StartPosition();
selection_.direction_ = Direction::kBackward;
DCHECK_GT(selection_.anchor_, selection_.focus_);
#if DCHECK_IS_ON()
selection_.dom_tree_version_ = range.GetDocument().DomTreeVersion();
#endif
return *this;
}
template <typename Strategy>
typename SelectionTemplate<Strategy>::Builder&
SelectionTemplate<Strategy>::Builder::SetAsForwardSelection(
const EphemeralRangeTemplate<Strategy>& range) {
DCHECK(range.IsNotNull());
DCHECK(selection_.IsNone()) << selection_;
selection_.anchor_ = range.StartPosition();
selection_.focus_ = range.EndPosition();
selection_.direction_ = Direction::kForward;
DCHECK_LE(selection_.anchor_, selection_.focus_);
#if DCHECK_IS_ON()
selection_.dom_tree_version_ = range.GetDocument().DomTreeVersion();
#endif
return *this;
}
template <typename Strategy>
typename SelectionTemplate<Strategy>::Builder&
SelectionTemplate<Strategy>::Builder::SetBaseAndExtent(
const EphemeralRangeTemplate<Strategy>& range) {
if (range.IsNull()) {
selection_.anchor_ = PositionTemplate<Strategy>();
selection_.focus_ = PositionTemplate<Strategy>();
#if DCHECK_IS_ON()
selection_.dom_tree_version_ = 0;
#endif
return *this;
}
return SetAsForwardSelection(range);
}
template <typename Strategy>
typename SelectionTemplate<Strategy>::Builder&
SelectionTemplate<Strategy>::Builder::SetBaseAndExtent(
const PositionTemplate<Strategy>& base,
const PositionTemplate<Strategy>& extent) {
if (base.IsNull()) {
DCHECK(extent.IsNull()) << extent;
return SetBaseAndExtent(EphemeralRangeTemplate<Strategy>());
}
// TODO(crbug.com/1423127): `extent` is not expected to be `IsNull` but it
// looks like there are such cases.
return Collapse(base).Extend(extent);
}
template <typename Strategy>
typename SelectionTemplate<Strategy>::Builder&
SelectionTemplate<Strategy>::Builder::SetBaseAndExtentDeprecated(
const PositionTemplate<Strategy>& base,
const PositionTemplate<Strategy>& extent) {
if (base.IsNotNull() && extent.IsNotNull()) {
return SetBaseAndExtent(base, extent);
}
if (base.IsNotNull())
return Collapse(base);
if (extent.IsNotNull())
return Collapse(extent);
return SetBaseAndExtent(EphemeralRangeTemplate<Strategy>());
}
// ---
template <typename Strategy>
SelectionTemplate<Strategy>::InvalidSelectionResetter::InvalidSelectionResetter(
const SelectionTemplate<Strategy>& selection)
: document_(selection.GetDocument()),
selection_(const_cast<SelectionTemplate&>(selection)) {
DCHECK(selection_.AssertValid());
}
template <typename Strategy>
SelectionTemplate<
Strategy>::InvalidSelectionResetter::~InvalidSelectionResetter() {
if (selection_.IsNone())
return;
DCHECK(document_);
if (!selection_.IsValidFor(*document_)) {
selection_ = SelectionTemplate<Strategy>();
return;
}
#if DCHECK_IS_ON()
selection_.dom_tree_version_ = document_->DomTreeVersion();
#endif
selection_.ResetDirectionCache();
}
SelectionInDOMTree ConvertToSelectionInDOMTree(
const SelectionInFlatTree& selection_in_flat_tree) {
return SelectionInDOMTree::Builder()
.SetAffinity(selection_in_flat_tree.Affinity())
.SetBaseAndExtent(ToPositionInDOMTree(selection_in_flat_tree.Anchor()),
ToPositionInDOMTree(selection_in_flat_tree.Focus()))
.Build();
}
SelectionInFlatTree ConvertToSelectionInFlatTree(
const SelectionInDOMTree& selection) {
SelectionInFlatTree::Builder builder;
const PositionInFlatTree& anchor = ToPositionInFlatTree(selection.Anchor());
const PositionInFlatTree& focus = ToPositionInFlatTree(selection.Focus());
if (anchor.IsConnected() && focus.IsConnected()) {
builder.SetBaseAndExtent(anchor, focus);
} else if (anchor.IsConnected()) {
builder.Collapse(anchor);
} else if (focus.IsConnected()) {
builder.Collapse(focus);
}
builder.SetAffinity(selection.Affinity());
return builder.Build();
}
template <typename Strategy>
void SelectionTemplate<Strategy>::InvalidSelectionResetter::Trace(
blink::Visitor* visitor) const {
visitor->Trace(document_);
}
template class CORE_TEMPLATE_EXPORT SelectionTemplate<EditingStrategy>;
template class CORE_TEMPLATE_EXPORT
SelectionTemplate<EditingInFlatTreeStrategy>;
} // namespace blink