blob: ccdfb73433681bbaf9ae70679568fb841610c2f3 [file] [log] [blame]
/*
* Copyright (C) 2004, 2005, 2006, 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 "third_party/blink/renderer/core/editing/position.h"
#include <stdio.h>
#include <ostream> // NOLINT
#include "third_party/blink/renderer/core/editing/editing_utilities.h"
#include "third_party/blink/renderer/core/editing/text_affinity.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
namespace blink {
#if DCHECK_IS_ON()
template <typename Strategy>
static bool CanBeAnchorNode(Node*);
template <>
bool CanBeAnchorNode<EditingStrategy>(Node* node) {
return !node || !node->IsPseudoElement();
}
template <>
bool CanBeAnchorNode<EditingInFlatTreeStrategy>(Node* node) {
return CanBeAnchorNode<EditingStrategy>(node) &&
(!node || node->CanParticipateInFlatTree());
}
#endif
template <typename Strategy>
void PositionTemplate<Strategy>::Trace(Visitor* visitor) const {
visitor->Trace(anchor_node_);
}
template <typename Strategy>
const TreeScope* PositionTemplate<Strategy>::CommonAncestorTreeScope(
const PositionTemplate<Strategy>& a,
const PositionTemplate<Strategy>& b) {
if (!a.ComputeContainerNode() || !b.ComputeContainerNode())
return nullptr;
return a.ComputeContainerNode()->GetTreeScope().CommonAncestorTreeScope(
b.ComputeContainerNode()->GetTreeScope());
}
template <typename Strategy>
PositionTemplate<Strategy> PositionTemplate<Strategy>::EditingPositionOf(
const Node* anchor_node,
int offset) {
if (!anchor_node || anchor_node->IsTextNode())
return PositionTemplate<Strategy>(anchor_node, offset);
if (!EditingIgnoresContent(*anchor_node)) {
return PositionTemplate<Strategy>::CreateWithoutValidationDeprecated(
*anchor_node, offset);
}
if (offset == 0)
return PositionTemplate<Strategy>(anchor_node,
PositionAnchorType::kBeforeAnchor);
// Note: |offset| can be >= 1, if |anchorNode| have child nodes, e.g.
// using Node.appendChild() to add a child node TEXTAREA.
DCHECK_GE(offset, 1);
return PositionTemplate<Strategy>(anchor_node,
PositionAnchorType::kAfterAnchor);
}
// TODO(editing-dev): Once we change type of |anchor_node_| to
// |Member<const Node>|, we should get rid of |const_cast<Node*>()|.
// See http://crbug.com/735327
template <typename Strategy>
PositionTemplate<Strategy>::PositionTemplate(const Node* anchor_node,
PositionAnchorType anchor_type)
: anchor_node_(const_cast<Node*>(anchor_node)),
offset_(0),
anchor_type_(anchor_type) {
#if DCHECK_IS_ON()
DCHECK(anchor_node_);
DCHECK_NE(anchor_type_, PositionAnchorType::kOffsetInAnchor);
DCHECK(CanBeAnchorNode<Strategy>(anchor_node_.Get())) << anchor_node_;
if (anchor_node_->IsTextNode()) {
DCHECK(anchor_type_ == PositionAnchorType::kBeforeAnchor ||
anchor_type_ == PositionAnchorType::kAfterAnchor)
<< *this;
return;
}
if (!Strategy::Parent(*anchor_node_)) {
// Before/After |anchor_node_| should have a parent node for converting
// to offset in anchor position.
DCHECK(IsBeforeChildren() || IsAfterChildren()) << *this;
return;
}
#endif
}
// TODO(editing-dev): Once we change type of |anchor_node_| to
// |Member<const Node>|, we should get rid of |const_cast<Node*>()|.
// See http://crbug.com/735327
template <typename Strategy>
PositionTemplate<Strategy>::PositionTemplate(const Node* anchor_node,
int offset)
: anchor_node_(const_cast<Node*>(anchor_node)),
offset_(offset),
anchor_type_(PositionAnchorType::kOffsetInAnchor) {
#if DCHECK_IS_ON()
DCHECK(CanBeAnchorNode<Strategy>(anchor_node_.Get())) << anchor_node_;
if (!anchor_node_) {
DCHECK_EQ(offset, 0);
return;
}
if (auto* data = DynamicTo<CharacterData>(anchor_node_.Get())) {
DCHECK_GE(offset, 0);
DCHECK_LE(static_cast<unsigned>(offset), data->length()) << anchor_node_;
return;
}
DCHECK_GE(offset, 0);
DCHECK_LE(static_cast<unsigned>(offset),
Strategy::CountChildren(*anchor_node))
<< anchor_node_;
#endif
}
template <typename Strategy>
PositionTemplate<Strategy>::PositionTemplate(const Node& anchor_node,
int offset)
: PositionTemplate(&anchor_node, offset) {}
template <typename Strategy>
PositionTemplate<Strategy>::PositionTemplate(const PositionTemplate& other)
: anchor_node_(other.anchor_node_),
offset_(other.offset_),
anchor_type_(other.anchor_type_) {}
// static
template <typename Strategy>
PositionTemplate<Strategy> PositionTemplate<Strategy>::CreateWithoutValidation(
const Node& container,
int offset) {
PositionTemplate<Strategy> result(container, 0);
result.offset_ = offset;
return result;
}
// static
template <typename Strategy>
PositionTemplate<Strategy>
PositionTemplate<Strategy>::CreateWithoutValidationDeprecated(
const Node& container,
int offset) {
return CreateWithoutValidation(container, offset);
}
// --
template <typename Strategy>
Node* PositionTemplate<Strategy>::ComputeContainerNode() const {
if (!anchor_node_)
return nullptr;
switch (AnchorType()) {
case PositionAnchorType::kAfterChildren:
case PositionAnchorType::kOffsetInAnchor:
return anchor_node_.Get();
case PositionAnchorType::kBeforeAnchor:
case PositionAnchorType::kAfterAnchor: {
Node* const parent = Strategy::Parent(*anchor_node_);
// TODO(https://crbug.com/889737), Once we fix the issue, we should have
// |DCHECK(parent)|.
return parent;
}
}
NOTREACHED();
return nullptr;
}
template <typename Strategy>
static int MinOffsetForNode(Node* anchor_node, int offset) {
if (auto* data = DynamicTo<CharacterData>(anchor_node))
return std::min(offset, static_cast<int>(data->length()));
int new_offset = 0;
for (Node* node = Strategy::FirstChild(*anchor_node);
node && new_offset < offset; node = Strategy::NextSibling(*node))
new_offset++;
return new_offset;
}
template <typename Strategy>
int PositionTemplate<Strategy>::ComputeOffsetInContainerNode() const {
if (!anchor_node_)
return 0;
switch (AnchorType()) {
case PositionAnchorType::kAfterChildren:
return LastOffsetInNode(*anchor_node_);
case PositionAnchorType::kOffsetInAnchor:
return MinOffsetForNode<Strategy>(anchor_node_.Get(), offset_);
case PositionAnchorType::kBeforeAnchor:
return Strategy::Index(*anchor_node_);
case PositionAnchorType::kAfterAnchor:
return Strategy::Index(*anchor_node_) + 1;
}
NOTREACHED();
return 0;
}
// Neighbor-anchored positions are invalid DOM positions, so they need to be
// fixed up before handing them off to the Range object.
template <typename Strategy>
PositionTemplate<Strategy>
PositionTemplate<Strategy>::ParentAnchoredEquivalent() const {
if (!anchor_node_)
return PositionTemplate<Strategy>();
// FIXME: This should only be necessary for legacy positions, but is also
// needed for positions before and after Tables
if (offset_ == 0 && !IsAfterAnchorOrAfterChildren()) {
if (Strategy::Parent(*anchor_node_) &&
(EditingIgnoresContent(*anchor_node_) ||
IsDisplayInsideTable(anchor_node_.Get())))
return InParentBeforeNode(*anchor_node_);
return PositionTemplate<Strategy>(anchor_node_.Get(), 0);
}
if (!anchor_node_->IsCharacterDataNode() &&
(IsAfterAnchorOrAfterChildren() ||
static_cast<unsigned>(offset_) == anchor_node_->CountChildren()) &&
(EditingIgnoresContent(*anchor_node_) ||
IsDisplayInsideTable(anchor_node_.Get())) &&
ComputeContainerNode()) {
return InParentAfterNode(*anchor_node_);
}
return PositionTemplate<Strategy>(ComputeContainerNode(),
ComputeOffsetInContainerNode());
}
template <typename Strategy>
PositionTemplate<Strategy> PositionTemplate<Strategy>::ToOffsetInAnchor()
const {
if (IsNull())
return PositionTemplate<Strategy>();
return PositionTemplate<Strategy>(ComputeContainerNode(),
ComputeOffsetInContainerNode());
}
template <typename Strategy>
int PositionTemplate<Strategy>::ComputeEditingOffset() const {
if (IsAfterAnchorOrAfterChildren())
return Strategy::LastOffsetForEditing(anchor_node_.Get());
return offset_;
}
template <typename Strategy>
Node* PositionTemplate<Strategy>::ComputeNodeBeforePosition() const {
if (!anchor_node_)
return nullptr;
switch (AnchorType()) {
case PositionAnchorType::kAfterChildren:
return Strategy::LastChild(*anchor_node_);
case PositionAnchorType::kOffsetInAnchor:
return offset_ ? Strategy::ChildAt(*anchor_node_, offset_ - 1) : nullptr;
case PositionAnchorType::kBeforeAnchor:
return Strategy::PreviousSibling(*anchor_node_);
case PositionAnchorType::kAfterAnchor:
return anchor_node_.Get();
}
NOTREACHED();
return nullptr;
}
template <typename Strategy>
Node* PositionTemplate<Strategy>::ComputeNodeAfterPosition() const {
if (!anchor_node_)
return nullptr;
switch (AnchorType()) {
case PositionAnchorType::kAfterChildren:
return nullptr;
case PositionAnchorType::kOffsetInAnchor:
return Strategy::ChildAt(*anchor_node_, offset_);
case PositionAnchorType::kBeforeAnchor:
return anchor_node_.Get();
case PositionAnchorType::kAfterAnchor:
return Strategy::NextSibling(*anchor_node_);
}
NOTREACHED();
return nullptr;
}
// An implementation of |Range::firstNode()|.
template <typename Strategy>
Node* PositionTemplate<Strategy>::NodeAsRangeFirstNode() const {
if (!anchor_node_)
return nullptr;
if (!IsOffsetInAnchor())
return ToOffsetInAnchor().NodeAsRangeFirstNode();
if (anchor_node_->IsCharacterDataNode())
return anchor_node_.Get();
if (Node* child = Strategy::ChildAt(*anchor_node_, offset_))
return child;
if (!offset_)
return anchor_node_.Get();
return Strategy::NextSkippingChildren(*anchor_node_);
}
template <typename Strategy>
Node* PositionTemplate<Strategy>::NodeAsRangeLastNode() const {
if (IsNull())
return nullptr;
if (Node* past_last_node = NodeAsRangePastLastNode())
return Strategy::Previous(*past_last_node);
return &Strategy::LastWithinOrSelf(*ComputeContainerNode());
}
// An implementation of |Range::pastLastNode()|.
template <typename Strategy>
Node* PositionTemplate<Strategy>::NodeAsRangePastLastNode() const {
if (!anchor_node_)
return nullptr;
if (!IsOffsetInAnchor())
return ToOffsetInAnchor().NodeAsRangePastLastNode();
if (anchor_node_->IsCharacterDataNode())
return Strategy::NextSkippingChildren(*anchor_node_);
if (Node* child = Strategy::ChildAt(*anchor_node_, offset_))
return child;
return Strategy::NextSkippingChildren(*anchor_node_);
}
template <typename Strategy>
Node* PositionTemplate<Strategy>::CommonAncestorContainer(
const PositionTemplate<Strategy>& other) const {
return Strategy::CommonAncestor(*ComputeContainerNode(),
*other.ComputeContainerNode());
}
static bool IsPositionConnected(const Position& position) {
return position.AnchorNode() && position.AnchorNode()->isConnected();
}
static bool IsPositionConnected(const PositionInFlatTree& position) {
if (position.IsNull())
return false;
return FlatTreeTraversal::Contains(*position.GetDocument(),
*position.AnchorNode());
}
template <typename Strategy>
bool PositionTemplate<Strategy>::IsBeforeChildren() const {
if (IsBeforeAnchor())
return !Strategy::PreviousSibling(*anchor_node_);
return IsOffsetInAnchor() && !offset_;
}
template <typename Strategy>
bool PositionTemplate<Strategy>::IsConnected() const {
return IsPositionConnected(*this);
}
template <typename Strategy>
bool PositionTemplate<Strategy>::IsValidFor(const Document& document) const {
if (IsNull())
return true;
if (GetDocument() != document)
return false;
if (!IsConnected())
return false;
return !IsOffsetInAnchor() ||
OffsetInContainerNode() <= LastOffsetInNode(*AnchorNode());
}
int16_t ComparePositions(const PositionInFlatTree& position_a,
const PositionInFlatTree& position_b) {
DCHECK(position_a.IsNotNull());
DCHECK(position_b.IsNotNull());
Node* container_a = position_a.ComputeContainerNode();
Node* container_b = position_b.ComputeContainerNode();
int offset_a = position_a.ComputeOffsetInContainerNode();
int offset_b = position_b.ComputeOffsetInContainerNode();
return ComparePositionsInFlatTree(container_a, offset_a, container_b,
offset_b);
}
template <typename Strategy>
int16_t PositionTemplate<Strategy>::CompareTo(
const PositionTemplate<Strategy>& other) const {
return ComparePositions(*this, other);
}
template <typename Strategy>
bool PositionTemplate<Strategy>::operator<(
const PositionTemplate<Strategy>& other) const {
return ComparePositions(*this, other) < 0;
}
template <typename Strategy>
bool PositionTemplate<Strategy>::operator<=(
const PositionTemplate<Strategy>& other) const {
return ComparePositions(*this, other) <= 0;
}
template <typename Strategy>
bool PositionTemplate<Strategy>::operator>(
const PositionTemplate<Strategy>& other) const {
return ComparePositions(*this, other) > 0;
}
template <typename Strategy>
bool PositionTemplate<Strategy>::operator>=(
const PositionTemplate<Strategy>& other) const {
return ComparePositions(*this, other) >= 0;
}
template <typename Strategy>
bool PositionTemplate<Strategy>::IsEquivalent(
const PositionTemplate<Strategy>& other) const {
if (IsNull())
return other.IsNull();
if (anchor_type_ == other.anchor_type_)
return *this == other;
return ToOffsetInAnchor() == other.ToOffsetInAnchor();
}
template <typename Strategy>
bool PositionTemplate<Strategy>::AtFirstEditingPositionForNode() const {
if (IsNull())
return true;
// FIXME: Position before anchor shouldn't be considered as at the first
// editing position for node since that position resides outside of the node.
switch (anchor_type_) {
case PositionAnchorType::kOffsetInAnchor:
return offset_ == 0;
case PositionAnchorType::kBeforeAnchor:
return true;
case PositionAnchorType::kAfterChildren:
case PositionAnchorType::kAfterAnchor:
// TODO(yosin) We should use |Strategy::lastOffsetForEditing()| instead
// of DOM tree version.
return !EditingStrategy::LastOffsetForEditing(AnchorNode());
}
NOTREACHED();
return false;
}
template <typename Strategy>
bool PositionTemplate<Strategy>::AtLastEditingPositionForNode() const {
if (IsNull())
return true;
// TODO(yosin): Position after anchor shouldn't be considered as at the
// first editing position for node since that position resides outside of
// the node.
// TODO(yosin) We should use |Strategy::lastOffsetForEditing()| instead of
// DOM tree version.
return IsAfterAnchorOrAfterChildren() ||
offset_ >= EditingStrategy::LastOffsetForEditing(AnchorNode());
}
template <typename Strategy>
bool PositionTemplate<Strategy>::AtStartOfTree() const {
if (IsNull())
return true;
return !Strategy::Parent(*AnchorNode()) && offset_ == 0;
}
template <typename Strategy>
bool PositionTemplate<Strategy>::AtEndOfTree() const {
if (IsNull())
return true;
// TODO(yosin) We should use |Strategy::lastOffsetForEditing()| instead of
// DOM tree version.
return !Strategy::Parent(*AnchorNode()) &&
offset_ >= EditingStrategy::LastOffsetForEditing(AnchorNode());
}
// static
template <typename Strategy>
PositionTemplate<Strategy> PositionTemplate<Strategy>::InParentBeforeNode(
const Node& node) {
// FIXME: This should DCHECK(node.parentNode()). At least one caller currently
// hits this DCHECK though, which indicates that the caller is trying to make
// a position relative to a disconnected node (which is likely an error)
// Specifically, editing/deleting/delete-ligature-001.html crashes with
// DCHECK(node->parentNode())
return PositionTemplate<Strategy>(Strategy::Parent(node),
Strategy::Index(node));
}
// static
template <typename Strategy>
PositionTemplate<Strategy> PositionTemplate<Strategy>::InParentAfterNode(
const Node& node) {
DCHECK(node.parentNode()) << node;
return PositionTemplate<Strategy>(Strategy::Parent(node),
Strategy::Index(node) + 1);
}
// static
template <typename Strategy>
PositionTemplate<Strategy> PositionTemplate<Strategy>::BeforeNode(
const Node& anchor_node) {
return PositionTemplate<Strategy>(&anchor_node,
PositionAnchorType::kBeforeAnchor);
}
// static
template <typename Strategy>
PositionTemplate<Strategy> PositionTemplate<Strategy>::AfterNode(
const Node& anchor_node) {
return PositionTemplate<Strategy>(&anchor_node,
PositionAnchorType::kAfterAnchor);
}
// static
template <typename Strategy>
int PositionTemplate<Strategy>::LastOffsetInNode(const Node& node) {
if (auto* data = DynamicTo<CharacterData>(node))
return static_cast<int>(data->length());
return static_cast<int>(Strategy::CountChildren(node));
}
// static
template <typename Strategy>
PositionTemplate<Strategy> PositionTemplate<Strategy>::FirstPositionInNode(
const Node& anchor_node) {
return PositionTemplate<Strategy>(anchor_node, 0);
}
// static
template <typename Strategy>
PositionTemplate<Strategy> PositionTemplate<Strategy>::LastPositionInNode(
const Node& anchor_node) {
if (anchor_node.IsTextNode()) {
return PositionTemplate<Strategy>(anchor_node,
LastOffsetInNode(anchor_node));
}
return PositionTemplate<Strategy>(&anchor_node,
PositionAnchorType::kAfterChildren);
}
// static
template <typename Strategy>
PositionTemplate<Strategy>
PositionTemplate<Strategy>::FirstPositionInOrBeforeNode(const Node& node) {
return EditingIgnoresContent(node) ? BeforeNode(node)
: FirstPositionInNode(node);
}
// static
template <typename Strategy>
PositionTemplate<Strategy>
PositionTemplate<Strategy>::LastPositionInOrAfterNode(const Node& node) {
return EditingIgnoresContent(node) ? AfterNode(node)
: LastPositionInNode(node);
}
PositionInFlatTree ToPositionInFlatTree(const Position& pos) {
if (pos.IsNull())
return PositionInFlatTree();
Node* const anchor = pos.AnchorNode();
if (pos.IsOffsetInAnchor()) {
if (anchor->IsCharacterDataNode())
return PositionInFlatTree(anchor, pos.ComputeOffsetInContainerNode());
DCHECK(!anchor->IsElementNode() || anchor->CanParticipateInFlatTree());
int offset = pos.ComputeOffsetInContainerNode();
if (!offset) {
Node* node = anchor->IsShadowRoot() ? anchor->OwnerShadowHost() : anchor;
return PositionInFlatTree::FirstPositionInNode(*node);
}
Node* child = NodeTraversal::ChildAt(*anchor, offset);
if (!child) {
Node* node = anchor->IsShadowRoot() ? anchor->OwnerShadowHost() : anchor;
return PositionInFlatTree::LastPositionInNode(*node);
}
if (!child->CanParticipateInFlatTree()) {
if (anchor->IsShadowRoot())
return PositionInFlatTree(anchor->OwnerShadowHost(), offset);
return PositionInFlatTree(anchor, offset);
}
if (Node* parent = FlatTreeTraversal::Parent(*child))
return PositionInFlatTree(parent, FlatTreeTraversal::Index(*child));
// When |pos| isn't appeared in flat tree, we map |pos| to after
// children of shadow host.
// e.g. "foo",0 in <progress>foo</progress>
if (anchor->IsShadowRoot()) {
return PositionInFlatTree::LastPositionInNode(*anchor->OwnerShadowHost());
}
return PositionInFlatTree::LastPositionInNode(*anchor);
}
if (anchor->IsShadowRoot())
return PositionInFlatTree(anchor->OwnerShadowHost(), pos.AnchorType());
DCHECK(anchor->CanParticipateInFlatTree());
if (pos.IsBeforeAnchor() || pos.IsAfterAnchor()) {
if (!FlatTreeTraversal::Parent(*anchor)) {
// For Before/AfterAnchor, if |anchor| doesn't have parent in the flat
// tree, there is no valid corresponding PositionInFlatTree.
// Since this function is a primitive function, we do not adjust |pos|
// to somewhere else in flat tree.
// Reached by unit test
// FrameSelectionTest.SelectInvalidPositionInFlatTreeDoesntCrash.
return PositionInFlatTree();
}
}
// TODO(yosin): Once we have a test case for SLOT or active insertion point,
// this function should handle it.
return PositionInFlatTree(anchor, pos.AnchorType());
}
PositionInFlatTree ToPositionInFlatTree(const PositionInFlatTree& position) {
return position;
}
Position ToPositionInDOMTree(const Position& position) {
return position;
}
Position ToPositionInDOMTree(const PositionInFlatTree& position) {
if (position.IsNull())
return Position();
Node* anchor_node = position.AnchorNode();
switch (position.AnchorType()) {
case PositionAnchorType::kAfterChildren:
// FIXME: When anchorNode is <img>, assertion fails in the constructor.
return Position::LastPositionInNode(*anchor_node);
case PositionAnchorType::kAfterAnchor:
return Position::AfterNode(*anchor_node);
case PositionAnchorType::kBeforeAnchor:
return Position::BeforeNode(*anchor_node);
case PositionAnchorType::kOffsetInAnchor: {
int offset = position.OffsetInContainerNode();
if (anchor_node->IsCharacterDataNode())
return Position(anchor_node, offset);
if (!offset)
return Position::FirstPositionInNode(*anchor_node);
Node* child = FlatTreeTraversal::ChildAt(*anchor_node, offset);
if (child)
return Position(child->parentNode(), child->NodeIndex());
// |child| is null when the position is at the end of the children.
// <div>foo|</div>
return Position::LastPositionInNode(*anchor_node);
}
default:
NOTREACHED();
return Position();
}
}
template <typename Strategy>
String PositionTemplate<Strategy>::ToAnchorTypeAndOffsetString() const {
switch (AnchorType()) {
case PositionAnchorType::kOffsetInAnchor: {
StringBuilder builder;
builder.Append("offsetInAnchor[");
builder.AppendNumber(offset_);
builder.Append("]");
return builder.ToString();
}
case PositionAnchorType::kAfterChildren:
return "afterChildren";
case PositionAnchorType::kBeforeAnchor:
return "beforeAnchor";
case PositionAnchorType::kAfterAnchor:
return "afterAnchor";
}
NOTREACHED();
return g_empty_string;
}
#if DCHECK_IS_ON()
template <typename Strategy>
void PositionTemplate<Strategy>::ShowTreeForThis() const {
if (!AnchorNode()) {
LOG(INFO) << "\nposition is null";
return;
}
LOG(INFO) << "\n"
<< AnchorNode()->ToTreeStringForThis().Utf8()
<< ToAnchorTypeAndOffsetString().Utf8();
}
template <typename Strategy>
void PositionTemplate<Strategy>::ShowTreeForThisInFlatTree() const {
if (!AnchorNode()) {
LOG(INFO) << "\nposition is null";
return;
}
LOG(INFO) << "\n"
<< AnchorNode()->ToFlatTreeStringForThis().Utf8()
<< ToAnchorTypeAndOffsetString().Utf8();
}
#endif // DCHECK_IS_ON()
template <typename PositionType>
static std::ostream& PrintPosition(std::ostream& ostream,
const PositionType& position) {
if (position.IsNull())
return ostream << "null";
return ostream << position.AnchorNode() << "@"
<< position.ToAnchorTypeAndOffsetString().Utf8();
}
std::ostream& operator<<(std::ostream& ostream,
PositionAnchorType anchor_type) {
switch (anchor_type) {
case PositionAnchorType::kAfterAnchor:
return ostream << "afterAnchor";
case PositionAnchorType::kAfterChildren:
return ostream << "afterChildren";
case PositionAnchorType::kBeforeAnchor:
return ostream << "beforeAnchor";
case PositionAnchorType::kOffsetInAnchor:
return ostream << "offsetInAnchor";
}
NOTREACHED();
return ostream << "anchorType=" << static_cast<int>(anchor_type);
}
std::ostream& operator<<(std::ostream& ostream, const Position& position) {
return PrintPosition(ostream, position);
}
std::ostream& operator<<(std::ostream& ostream,
const PositionInFlatTree& position) {
return PrintPosition(ostream, position);
}
template class CORE_TEMPLATE_EXPORT PositionTemplate<EditingStrategy>;
template class CORE_TEMPLATE_EXPORT PositionTemplate<EditingInFlatTreeStrategy>;
} // namespace blink
#if DCHECK_IS_ON()
void showTree(const blink::Position& pos) {
pos.ShowTreeForThis();
}
void showTree(const blink::Position* pos) {
if (pos)
pos->ShowTreeForThis();
else
LOG(INFO) << "Cannot showTree for <null>";
}
#endif