blob: e55ae2813579091898bb01c588659651cbf275fb [file] [log] [blame]
/*
* Copyright (C) 2007, 2008 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 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 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_iterator.h"
#include "base/numerics/safe_conversions.h"
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/dom/text.h"
#include "third_party/blink/renderer/core/editing/editing_utilities.h"
#include "third_party/blink/renderer/core/editing/position.h"
namespace blink {
namespace {
// TODO(editing-dev): We should replace usages of |hasChildren()| in
// |PositionIterator| to |shouldTraverseChildren()|.
template <typename Strategy>
bool ShouldTraverseChildren(const Node& node) {
return Strategy::HasChildren(node) && !IsUserSelectContain(node);
}
template <typename Strategy>
int LastOffsetForPositionIterator(const Node* node) {
return IsUserSelectContain(*node) ? 1 : Strategy::LastOffsetForEditing(node);
}
// TODO(editing-dev): We should replace usages of |parent()| in
// |PositionIterator| to |selectableParentOf()|.
template <typename Strategy>
ContainerNode* SelectableParentOf(const Node& node) {
ContainerNode* const parent = Strategy::Parent(node);
return parent && !IsUserSelectContain(*parent) ? parent : nullptr;
}
} // namespace
static constexpr int kInvalidOffset = -1;
template <typename Strategy>
SlowPositionIteratorAlgorithm<Strategy>::SlowPositionIteratorAlgorithm(
const PositionTemplate<Strategy>& pos) {
if (pos.IsNull())
return;
anchor_node_ = pos.AnchorNode();
const int offset_in_anchor = pos.ComputeEditingOffset();
node_after_position_in_anchor_ =
Strategy::ChildAt(*anchor_node_, offset_in_anchor);
offset_in_anchor_ = node_after_position_in_anchor_ ? 0 : offset_in_anchor;
dom_tree_version_ = anchor_node_->GetDocument().DomTreeVersion();
for (Node* node = SelectableParentOf<Strategy>(*anchor_node_); node;
node = SelectableParentOf<Strategy>(*node)) {
// Each offsets_in_anchor_node_[offset] should be an index of node in
// parent, but delay to calculate the index until it is needed for
// performance.
offsets_in_anchor_node_.push_back(kInvalidOffset);
++depth_to_anchor_node_;
}
if (node_after_position_in_anchor_)
offsets_in_anchor_node_.push_back(offset_in_anchor);
}
template <typename Strategy>
PositionTemplate<Strategy>
SlowPositionIteratorAlgorithm<Strategy>::DeprecatedComputePosition() const {
// TODO(yoichio): Share code to check domTreeVersion with EphemeralRange.
DCHECK(IsValid());
if (node_after_position_in_anchor_) {
DCHECK(anchor_node_);
DCHECK_EQ(Strategy::Parent(*node_after_position_in_anchor_), anchor_node_);
DCHECK_NE(offsets_in_anchor_node_[depth_to_anchor_node_], kInvalidOffset);
// FIXME: This check is inadaquete because any ancestor could be ignored by
// editing
if (EditingIgnoresContent(
*Strategy::Parent(*node_after_position_in_anchor_)))
return PositionTemplate<Strategy>::BeforeNode(*anchor_node_);
return PositionTemplate<Strategy>(
anchor_node_, offsets_in_anchor_node_[depth_to_anchor_node_]);
}
if (!anchor_node_)
return PositionTemplate<Strategy>();
if (Strategy::HasChildren(*anchor_node_)) {
return PositionTemplate<Strategy>::LastPositionInOrAfterNode(*anchor_node_);
}
return PositionTemplate<Strategy>::EditingPositionOf(anchor_node_,
offset_in_anchor_);
}
template <typename Strategy>
PositionTemplate<Strategy>
SlowPositionIteratorAlgorithm<Strategy>::ComputePosition() const {
DCHECK(IsValid());
// Assume that we have the following DOM tree:
// A
// |-B
// | |-E
// | +-F
// |
// |-C
// +-D
// |-G
// +-H
if (node_after_position_in_anchor_) {
// For example, position is before E, F.
DCHECK(anchor_node_);
DCHECK_EQ(Strategy::Parent(*node_after_position_in_anchor_), anchor_node_);
DCHECK_NE(offsets_in_anchor_node_[depth_to_anchor_node_], kInvalidOffset);
// TODO(yoichio): This should be equivalent to PositionTemplate<Strategy>(
// anchor_node_, PositionAnchorType::kBeforeAnchor).
return PositionTemplate<Strategy>(
anchor_node_, offsets_in_anchor_node_[depth_to_anchor_node_]);
}
if (!anchor_node_)
return PositionTemplate<Strategy>();
if (ShouldTraverseChildren<Strategy>(*anchor_node_)) {
// For example, position is the end of B.
return PositionTemplate<Strategy>::LastPositionInOrAfterNode(*anchor_node_);
}
if (anchor_node_->IsTextNode())
return PositionTemplate<Strategy>(anchor_node_, offset_in_anchor_);
if (offset_in_anchor_)
// For example, position is after G.
return PositionTemplate<Strategy>(anchor_node_,
PositionAnchorType::kAfterAnchor);
// For example, position is before G.
return PositionTemplate<Strategy>(anchor_node_,
PositionAnchorType::kBeforeAnchor);
}
template <typename Strategy>
void SlowPositionIteratorAlgorithm<Strategy>::Increment() {
DCHECK(IsValid());
if (!anchor_node_)
return;
// Assume that we have the following DOM tree:
// A
// |-B
// | |-E
// | +-F
// |
// |-C
// +-D
// |-G
// +-H
// Let |anchor| as |anchor_node_| and
// |child| as |node_after_position_in_anchor_|.
if (node_after_position_in_anchor_) {
// Case #1: Move to position before the first child of
// |node_after_position_in_anchor_|.
// This is a point just before |child|.
// Let |anchor| is A and |child| is B,
// then next |anchor| is B and |child| is E.
anchor_node_ = node_after_position_in_anchor_;
node_after_position_in_anchor_ =
ShouldTraverseChildren<Strategy>(*anchor_node_)
? Strategy::FirstChild(*anchor_node_)
: nullptr;
offset_in_anchor_ = 0;
// Increment depth intializing with 0.
++depth_to_anchor_node_;
if (depth_to_anchor_node_ == offsets_in_anchor_node_.size())
offsets_in_anchor_node_.push_back(0);
else
offsets_in_anchor_node_[depth_to_anchor_node_] = 0;
return;
}
if (anchor_node_->GetLayoutObject() &&
!ShouldTraverseChildren<Strategy>(*anchor_node_) &&
offset_in_anchor_ <
LastOffsetForPositionIterator<Strategy>(anchor_node_)) {
// Case #2. This is the next of Case #1 or #2 itself.
// Position is (|anchor|, |offset_in_anchor_|).
// In this case |anchor| is a leaf(E,F,C,G or H) and
// |offset_in_anchor_| is not on the end of |anchor|.
// Then just increment |offset_in_anchor_|.
offset_in_anchor_ =
NextGraphemeBoundaryOf(*anchor_node_, offset_in_anchor_);
} else {
// Case #3. This is the next of Case #2 or #3.
// Position is the end of |anchor|.
// 3-a. If |anchor| has next sibling (let E),
// next |anchor| is B and |child| is F (next is Case #1.)
// 3-b. If |anchor| doesn't have next sibling (let F),
// next |anchor| is B and |child| is null. (next is Case #3.)
node_after_position_in_anchor_ = anchor_node_;
anchor_node_ =
SelectableParentOf<Strategy>(*node_after_position_in_anchor_);
if (!anchor_node_)
return;
DCHECK_GT(depth_to_anchor_node_, 0u);
--depth_to_anchor_node_;
// Increment offset of |child| or initialize if it have never been
// used.
if (offsets_in_anchor_node_[depth_to_anchor_node_] == kInvalidOffset)
offsets_in_anchor_node_[depth_to_anchor_node_] =
Strategy::Index(*node_after_position_in_anchor_) + 1;
else
++offsets_in_anchor_node_[depth_to_anchor_node_];
node_after_position_in_anchor_ =
Strategy::NextSibling(*node_after_position_in_anchor_);
offset_in_anchor_ = offsets_in_anchor_node_[depth_to_anchor_node_];
}
}
template <typename Strategy>
void SlowPositionIteratorAlgorithm<Strategy>::Decrement() {
DCHECK(IsValid());
if (!anchor_node_)
return;
// Assume that we have the following DOM tree:
// A
// |-B
// | |-E
// | +-F
// |
// |-C
// +-D
// |-G
// +-H
// Let |anchor| as |anchor_node_| and
// |child| as |node_after_position_in_anchor_|.
// Decrement() is complex but logically reverse of Increment(), of course:)
if (node_after_position_in_anchor_) {
anchor_node_ = Strategy::PreviousSibling(*node_after_position_in_anchor_);
if (anchor_node_) {
// Case #1-a. This is a revese of Increment()::Case#3-a.
// |child| has a previous sibling.
// Let |anchor| is B and |child| is F,
// next |anchor| is E and |child| is null.
node_after_position_in_anchor_ = nullptr;
offset_in_anchor_ =
ShouldTraverseChildren<Strategy>(*anchor_node_)
? 0
: LastOffsetForPositionIterator<Strategy>(anchor_node_);
// Decrement offset of |child| or initialize if it have never been
// used.
if (offsets_in_anchor_node_[depth_to_anchor_node_] == kInvalidOffset)
offsets_in_anchor_node_[depth_to_anchor_node_] =
Strategy::Index(*node_after_position_in_anchor_);
else
--offsets_in_anchor_node_[depth_to_anchor_node_];
DCHECK_GE(offsets_in_anchor_node_[depth_to_anchor_node_], 0);
// Increment depth intializing with last offset.
++depth_to_anchor_node_;
if (depth_to_anchor_node_ >= offsets_in_anchor_node_.size())
offsets_in_anchor_node_.push_back(offset_in_anchor_);
else
offsets_in_anchor_node_[depth_to_anchor_node_] = offset_in_anchor_;
return;
} else {
// Case #1-b. This is a revese of Increment()::Case#1.
// |child| doesn't have a previous sibling.
// Let |anchor| is B and |child| is E,
// next |anchor| is A and |child| is B.
node_after_position_in_anchor_ =
Strategy::Parent(*node_after_position_in_anchor_);
anchor_node_ =
SelectableParentOf<Strategy>(*node_after_position_in_anchor_);
if (!anchor_node_)
return;
offset_in_anchor_ = 0;
// Decrement depth and intialize if needs.
DCHECK_GT(depth_to_anchor_node_, 0u);
--depth_to_anchor_node_;
if (offsets_in_anchor_node_[depth_to_anchor_node_] == kInvalidOffset)
offsets_in_anchor_node_[depth_to_anchor_node_] =
Strategy::Index(*node_after_position_in_anchor_);
}
return;
}
if (ShouldTraverseChildren<Strategy>(*anchor_node_)) {
// Case #2. This is a reverse of increment()::Case3-b.
// Let |anchor| is B, next |anchor| is F.
anchor_node_ = Strategy::LastChild(*anchor_node_);
offset_in_anchor_ =
ShouldTraverseChildren<Strategy>(*anchor_node_)
? 0
: LastOffsetForPositionIterator<Strategy>(anchor_node_);
// Decrement depth initializing with -1 because
// |node_after_position_in_anchor_| is null so still unneeded.
if (depth_to_anchor_node_ >= offsets_in_anchor_node_.size())
offsets_in_anchor_node_.push_back(kInvalidOffset);
else
offsets_in_anchor_node_[depth_to_anchor_node_] = kInvalidOffset;
++depth_to_anchor_node_;
return;
}
if (offset_in_anchor_ && anchor_node_->GetLayoutObject()) {
// Case #3-a. This is a reverse of Increment()::Case#2.
// In this case |anchor| is a leaf(E,F,C,G or H) and
// |offset_in_anchor_| is not on the beginning of |anchor|.
// Then just decrement |offset_in_anchor_|.
offset_in_anchor_ =
PreviousGraphemeBoundaryOf(*anchor_node_, offset_in_anchor_);
return;
}
// Case #3-b. This is a reverse of Increment()::Case#1.
// In this case |anchor| is a leaf(E,F,C,G or H) and
// |offset_in_anchor_| is on the beginning of |anchor|.
// Let |anchor| is E,
// next |anchor| is B and |child| is E.
node_after_position_in_anchor_ = anchor_node_;
anchor_node_ = SelectableParentOf<Strategy>(*anchor_node_);
if (!anchor_node_)
return;
DCHECK_GT(depth_to_anchor_node_, 0u);
--depth_to_anchor_node_;
if (offsets_in_anchor_node_[depth_to_anchor_node_] != kInvalidOffset)
return;
offset_in_anchor_ = Strategy::Index(*node_after_position_in_anchor_);
offsets_in_anchor_node_[depth_to_anchor_node_] = offset_in_anchor_;
}
template <typename Strategy>
bool SlowPositionIteratorAlgorithm<Strategy>::AtStart() const {
DCHECK(IsValid());
if (!anchor_node_)
return true;
if (Strategy::Parent(*anchor_node_))
return false;
return (!Strategy::HasChildren(*anchor_node_) && !offset_in_anchor_) ||
(node_after_position_in_anchor_ &&
!Strategy::PreviousSibling(*node_after_position_in_anchor_));
}
template <typename Strategy>
bool SlowPositionIteratorAlgorithm<Strategy>::AtEnd() const {
DCHECK(IsValid());
if (!anchor_node_)
return true;
if (node_after_position_in_anchor_)
return false;
return !Strategy::Parent(*anchor_node_) &&
(Strategy::HasChildren(*anchor_node_) ||
offset_in_anchor_ >= Strategy::LastOffsetForEditing(anchor_node_));
}
template <typename Strategy>
bool SlowPositionIteratorAlgorithm<Strategy>::AtStartOfNode() const {
DCHECK(IsValid());
if (!anchor_node_)
return true;
if (!node_after_position_in_anchor_) {
return !ShouldTraverseChildren<Strategy>(*anchor_node_) &&
!offset_in_anchor_;
}
return !Strategy::PreviousSibling(*node_after_position_in_anchor_);
}
template <typename Strategy>
bool SlowPositionIteratorAlgorithm<Strategy>::AtEndOfNode() const {
DCHECK(IsValid());
if (!anchor_node_)
return true;
if (node_after_position_in_anchor_)
return false;
return Strategy::HasChildren(*anchor_node_) ||
offset_in_anchor_ >= Strategy::LastOffsetForEditing(anchor_node_);
}
template class CORE_TEMPLATE_EXPORT
SlowPositionIteratorAlgorithm<EditingStrategy>;
template class CORE_TEMPLATE_EXPORT
SlowPositionIteratorAlgorithm<EditingInFlatTreeStrategy>;
// ---
// static
template <typename Strategy>
typename FastPositionIteratorAlgorithm<Strategy>::ContainerType
FastPositionIteratorAlgorithm<Strategy>::ContainerToContainerType(
const Node* node) {
if (!node)
return kNullNode;
if (IsA<Text>(node) && node->GetLayoutObject())
return kTextNode;
if (IsA<CharacterData>(node))
return kCharacterData;
if (!Strategy::HasChildren(*node))
return kNoChildren;
if (::blink::IsUserSelectContain(*node))
return kUserSelectContainNode;
return kContainerNode;
}
template <typename Strategy>
FastPositionIteratorAlgorithm<Strategy>::FastPositionIteratorAlgorithm(
const PositionType& position) {
Initialize(position);
AssertOffsetInContainerIsValid();
}
template <typename Strategy>
void FastPositionIteratorAlgorithm<Strategy>::Initialize(
const PositionType& position) {
container_node_ = position.AnchorNode();
if (!container_node_)
return;
dom_tree_version_ = container_node_->GetDocument().DomTreeVersion();
container_type_ = ContainerToContainerType(container_node_);
switch (container_type_) {
case kNullNode:
NOTREACHED();
return;
case kNoChildren:
switch (position.AnchorType()) {
case PositionAnchorType::kAfterChildren:
case PositionAnchorType::kAfterAnchor:
offset_in_container_ = IgnoresChildren() ? 1 : 0;
return;
case PositionAnchorType::kBeforeAnchor:
offset_in_container_ = 0;
return;
case PositionAnchorType::kOffsetInAnchor:
DCHECK(!position.OffsetInContainerNode());
offset_in_container_ = 0;
return;
}
NOTREACHED() << "Invalid PositionAnchorType";
return;
case kCharacterData:
case kTextNode:
// Note: `Position::ComputeOffsetInContainer()` for `kAfterAnchor`
// returns `container_node_->Index() + 1` instead of `Text::length()`.
switch (position.AnchorType()) {
case PositionAnchorType::kAfterChildren:
NOTREACHED();
break;
case PositionAnchorType::kAfterAnchor:
offset_in_container_ = To<CharacterData>(container_node_)->length();
return;
case PositionAnchorType::kBeforeAnchor:
offset_in_container_ = 0;
return;
case PositionAnchorType::kOffsetInAnchor:
offset_in_container_ = position.OffsetInContainerNode();
return;
}
NOTREACHED() << "Invalid PositionAnchorType";
return;
case kContainerNode:
case kUserSelectContainNode:
container_type_ = kContainerNode;
switch (position.AnchorType()) {
case PositionAnchorType::kAfterChildren:
case PositionAnchorType::kAfterAnchor:
child_before_position_ = Strategy::LastChild(*container_node_);
offset_in_container_ = child_before_position_ ? kInvalidOffset : 0;
container_type_ = kContainerNode;
return;
case PositionAnchorType::kBeforeAnchor:
child_before_position_ = nullptr;
offset_in_container_ = 0;
container_type_ = kContainerNode;
return;
case PositionAnchorType::kOffsetInAnchor:
// This takes `O(position.OffsetInContainerNode())`.
child_before_position_ = position.ComputeNodeBeforePosition();
offset_in_container_ = position.OffsetInContainerNode();
container_type_ = kContainerNode;
return;
}
NOTREACHED() << " Invalid PositionAnchorType=" << position.AnchorType();
}
NOTREACHED() << " Invalid container_type_=" << container_type_;
}
template <typename Strategy>
FastPositionIteratorAlgorithm<Strategy>::FastPositionIteratorAlgorithm() =
default;
template <typename Strategy>
void FastPositionIteratorAlgorithm<Strategy>::AssertOffsetInContainerIsValid()
const {
#if DCHECK_IS_ON()
switch (container_type_) {
case kNullNode:
DCHECK(!child_before_position_);
DCHECK_EQ(offset_in_container_, kInvalidOffset);
return;
case kNoChildren:
DCHECK(!child_before_position_);
DCHECK(offset_in_container_ == 0 || offset_in_container_ == 1);
return;
case kCharacterData:
case kTextNode:
DCHECK(!child_before_position_);
DCHECK_LE(offset_in_container_,
To<CharacterData>(container_node_)->length());
return;
case kContainerNode:
case kUserSelectContainNode:
if (!child_before_position_) {
DCHECK(!offset_in_container_);
return;
}
if (offset_in_container_ == kInvalidOffset)
return;
DCHECK_EQ(offset_in_container_,
Strategy::Index(*child_before_position_) + 1);
return;
}
NOTREACHED() << " Invalid container_type_=" << container_type_;
#endif
}
template <typename Strategy>
void FastPositionIteratorAlgorithm<Strategy>::AssertOffsetStackIsValid() const {
#if DCHECK_IS_ON()
const auto* it = offset_stack_.begin();
for (const Node& ancestor : Strategy::AncestorsOf(*container_node_)) {
if (it == offset_stack_.end())
break;
DCHECK_EQ(*it, Strategy::Index(ancestor)) << " " << ancestor;
++it;
}
#endif
}
template <typename Strategy>
bool FastPositionIteratorAlgorithm<Strategy>::IsValid() const {
if (container_node_ &&
container_node_->GetDocument().DomTreeVersion() != dom_tree_version_)
return false;
AssertOffsetInContainerIsValid();
return true;
}
template <typename Strategy>
Node* FastPositionIteratorAlgorithm<Strategy>::ChildAfterPosition() const {
DCHECK(container_type_ == kContainerNode ||
container_type_ == kUserSelectContainNode);
return child_before_position_ ? Strategy::NextSibling(*child_before_position_)
: Strategy::FirstChild(*container_node_);
}
template <typename Strategy>
bool FastPositionIteratorAlgorithm<Strategy>::HasChildren() const {
DCHECK(container_node_);
return Strategy::HasChildren(*container_node_);
}
template <typename Strategy>
bool FastPositionIteratorAlgorithm<Strategy>::IgnoresChildren() const {
DCHECK(container_node_);
return EditingIgnoresContent(*container_node_);
}
template <typename Strategy>
bool FastPositionIteratorAlgorithm<Strategy>::IsUserSelectContain() const {
DCHECK(container_node_);
return ::blink::IsUserSelectContain(*container_node_);
}
template <typename Strategy>
void FastPositionIteratorAlgorithm<Strategy>::Decrement() {
AssertOffsetInContainerIsValid();
DecrementInternal();
AssertOffsetInContainerIsValid();
}
template <typename Strategy>
void FastPositionIteratorAlgorithm<Strategy>::Increment() {
AssertOffsetInContainerIsValid();
IncrementInternal();
AssertOffsetInContainerIsValid();
}
template <typename Strategy>
void FastPositionIteratorAlgorithm<Strategy>::DecrementInternal() {
DCHECK(IsValid());
switch (container_type_) {
case kNullNode:
return;
case kNoChildren:
if (!offset_in_container_ || !container_node_->GetLayoutObject())
return MoveToPreviousContainer();
offset_in_container_ = 0;
return;
case kCharacterData:
return MoveToPreviousContainer();
case kContainerNode:
if (!child_before_position_)
return MoveToPreviousContainer();
if (IsUserSelectContain()) {
if (!container_node_->GetLayoutObject())
return MoveToPreviousContainer();
if (!ChildAfterPosition()) {
container_type_ = kUserSelectContainNode;
return MoveToPreviousSkippingChildren();
}
// TODO(crbug.com/1132412): We should move to before children.
}
MoveOffsetInContainerBy(-1);
SetContainer(child_before_position_);
switch (container_type_) {
case kNoChildren:
child_before_position_ = nullptr;
PushThenSetOffset(IgnoresChildren() ? 1 : 0);
return;
case kCharacterData:
case kTextNode:
child_before_position_ = nullptr;
PushThenSetOffset(To<CharacterData>(container_node_)->length());
return;
case kContainerNode:
child_before_position_ = Strategy::LastChild(*container_node_);
PushThenSetOffset(kInvalidOffset);
return;
case kUserSelectContainNode:
// TODO(crbug.com/1132412): We should move to before children.
child_before_position_ = Strategy::FirstChild(*container_node_);
PushThenSetOffset(child_before_position_ ? 1 : 0);
return;
case kNullNode:
NOTREACHED() << " Unexpected container_type_=" << container_type_;
return;
}
NOTREACHED() << " Invalid container_type_=" << container_type_;
return;
case kTextNode:
if (!offset_in_container_)
return MoveToPreviousContainer();
offset_in_container_ =
PreviousGraphemeBoundaryOf(*container_node_, offset_in_container_);
return;
case kUserSelectContainNode:
// TODO(crbug.com/1132412): We should move to next container
// unconditionally.
if (!container_node_->GetLayoutObject())
return MoveToPreviousContainer();
return MoveToPreviousSkippingChildren();
}
NOTREACHED() << " Invalid container_type_=" << container_type_;
}
template <typename Strategy>
void FastPositionIteratorAlgorithm<Strategy>::IncrementInternal() {
DCHECK(IsValid());
switch (container_type_) {
case kNullNode:
return;
case kNoChildren:
if (offset_in_container_ || !container_node_->GetLayoutObject() ||
!IgnoresChildren())
return MoveToNextContainer();
offset_in_container_ = 1;
return;
case kCharacterData:
return MoveToNextContainer();
case kContainerNode:
if (!ChildAfterPosition())
return MoveToNextContainer();
MoveOffsetInContainerBy(1);
child_before_position_ = ChildAfterPosition();
SetContainer(child_before_position_);
child_before_position_ = nullptr;
return PushThenSetOffset(0);
case kTextNode:
if (offset_in_container_ == To<Text>(container_node_)->length())
return MoveToNextContainer();
offset_in_container_ =
NextGraphemeBoundaryOf(*container_node_, offset_in_container_);
return;
case kUserSelectContainNode:
// TODO(crbug.com/1132412): We should move to next container
// unconditionally.
if (!container_node_->GetLayoutObject())
return MoveToNextContainer();
// Note: We should skip to next container after visiting first child,
// because `LastOffsetForPositionIterator()` returns 1.
if (child_before_position_ == Strategy::FirstChild(*container_node_))
return MoveToNextContainer();
return MoveToNextSkippingChildren();
}
NOTREACHED() << " Invalid container_type_=" << container_type_;
}
template <typename Strategy>
void FastPositionIteratorAlgorithm<Strategy>::MoveToNextContainer() {
PopOffsetStack();
child_before_position_ = container_node_;
SetContainer(SelectableParentOf<Strategy>(*container_node_));
}
template <typename Strategy>
void FastPositionIteratorAlgorithm<Strategy>::MoveToNextSkippingChildren() {
if (child_before_position_ == Strategy::LastChild(*container_node_)) {
PopOffsetStack();
child_before_position_ = container_node_;
return SetContainer(SelectableParentOf<Strategy>(*container_node_));
}
MoveOffsetInContainerBy(1);
child_before_position_ = ChildAfterPosition();
}
template <typename Strategy>
void FastPositionIteratorAlgorithm<Strategy>::MoveToPreviousContainer() {
PopOffsetStack();
SetChildBeforePositionToPreviosuSigblingOf(*container_node_);
SetContainer(SelectableParentOf<Strategy>(*container_node_));
}
template <typename Strategy>
void FastPositionIteratorAlgorithm<Strategy>::MoveToPreviousSkippingChildren() {
if (!child_before_position_) {
PopOffsetStack();
SetChildBeforePositionToPreviosuSigblingOf(*container_node_);
return SetContainer(SelectableParentOf<Strategy>(*container_node_));
}
MoveOffsetInContainerBy(-1);
SetChildBeforePositionToPreviosuSigblingOf(*child_before_position_);
}
template <typename Strategy>
void FastPositionIteratorAlgorithm<
Strategy>::SetChildBeforePositionToPreviosuSigblingOf(const Node& node) {
child_before_position_ = Strategy::PreviousSibling(node);
if (child_before_position_) {
DCHECK(offset_in_container_);
return;
}
DCHECK(offset_in_container_ == kInvalidOffset || !offset_in_container_);
offset_in_container_ = 0;
}
template <typename Strategy>
void FastPositionIteratorAlgorithm<Strategy>::SetContainer(Node* node) {
container_node_ = node;
container_type_ = ContainerToContainerType(node);
if (container_type_ == kNullNode) {
child_before_position_ = nullptr;
offset_in_container_ = kInvalidOffset;
container_type_ = kNullNode;
}
}
template <typename Strategy>
PositionTemplate<Strategy>
FastPositionIteratorAlgorithm<Strategy>::BeforeOrAfterPosition() const {
DCHECK(IsValid());
return IsBeforePosition() ? PositionType::BeforeNode(*container_node_)
: PositionType::AfterNode(*container_node_);
}
template <typename Strategy>
bool FastPositionIteratorAlgorithm<Strategy>::IsBeforePosition() const {
DCHECK(IsValid());
switch (container_type_) {
case kNullNode:
case kTextNode:
NOTREACHED() << " Unexpected container_type_=" << container_type_;
return false;
case kNoChildren:
case kCharacterData:
case kUserSelectContainNode:
return !offset_in_container_;
case kContainerNode:
return !child_before_position_;
}
NOTREACHED() << " Invalid container_type_=" << container_type_;
return false;
}
template <typename Strategy>
PositionTemplate<Strategy>
FastPositionIteratorAlgorithm<Strategy>::DeprecatedComputePosition() const {
DCHECK(IsValid());
switch (container_type_) {
case kNullNode:
return PositionType();
case kNoChildren:
if (IgnoresChildren())
return BeforeOrAfterPosition();
DCHECK(!offset_in_container_);
return PositionType(*container_node_, 0);
case kCharacterData:
if (IsA<Text>(*container_node_))
return PositionType(*container_node_, offset_in_container_);
return BeforeOrAfterPosition();
case kContainerNode:
if (Node* child_after_position = ChildAfterPosition()) {
if (EditingIgnoresContent(*Strategy::Parent(*child_after_position)))
return PositionType::BeforeNode(*container_node_);
EnsureOffsetInContainer();
return PositionType(*container_node_, offset_in_container_);
}
return PositionType::LastPositionInOrAfterNode(*container_node_);
case kTextNode:
return PositionType(*container_node_, offset_in_container_);
case kUserSelectContainNode:
return PositionType::LastPositionInOrAfterNode(*container_node_);
}
NOTREACHED() << " Invalid container_type_=" << container_type_;
}
template <typename Strategy>
PositionTemplate<Strategy>
FastPositionIteratorAlgorithm<Strategy>::ComputePosition() const {
DCHECK(IsValid());
switch (container_type_) {
case kNullNode:
return PositionType();
case kNoChildren:
return BeforeOrAfterPosition();
case kCharacterData:
if (IsA<Text>(*container_node_))
return PositionType(*container_node_, offset_in_container_);
return BeforeOrAfterPosition();
case kContainerNode:
if (ChildAfterPosition()) {
EnsureOffsetInContainer();
return PositionType(*container_node_, offset_in_container_);
}
if (IsUserSelectContain())
return BeforeOrAfterPosition();
return PositionType::LastPositionInOrAfterNode(*container_node_);
case kTextNode:
return PositionType(*container_node_, offset_in_container_);
case kUserSelectContainNode:
return BeforeOrAfterPosition();
}
NOTREACHED() << " Invalid container_type_=" << container_type_;
}
template <typename Strategy>
int FastPositionIteratorAlgorithm<Strategy>::OffsetInTextNode() const {
DCHECK(IsValid());
//`VisiblePositionTest.PlaceholderBRWithCollapsedSpace` calls this function
// with `kCharacterData`.
DCHECK(container_type_ == kTextNode || container_type_ == kCharacterData)
<< container_type_;
DCHECK(IsA<Text>(container_node_)) << container_node_;
return base::saturated_cast<int>(offset_in_container_);
}
template <typename Strategy>
bool FastPositionIteratorAlgorithm<Strategy>::AtStart() const {
DCHECK(IsValid());
if (!container_node_)
return true;
return !Strategy::Parent(*container_node_) && AtStartOfNode();
}
template <typename Strategy>
bool FastPositionIteratorAlgorithm<Strategy>::AtEnd() const {
DCHECK(IsValid());
if (!container_node_)
return true;
return !Strategy::Parent(*container_node_) && AtEndOfNode();
}
template <typename Strategy>
bool FastPositionIteratorAlgorithm<Strategy>::AtStartOfNode() const {
DCHECK(IsValid());
switch (container_type_) {
case kNullNode:
return true;
case kContainerNode:
return !child_before_position_;
case kNoChildren:
case kCharacterData:
case kTextNode:
return !offset_in_container_;
case kUserSelectContainNode:
return !child_before_position_;
}
NOTREACHED() << " Invalid container_type_=" << container_type_;
}
template <typename Strategy>
bool FastPositionIteratorAlgorithm<Strategy>::AtEndOfNode() const {
DCHECK(IsValid());
switch (container_type_) {
case kNullNode:
return true;
case kContainerNode:
return !ChildAfterPosition();
case kNoChildren:
return !IgnoresChildren() || offset_in_container_;
case kCharacterData:
case kTextNode:
return offset_in_container_ ==
To<CharacterData>(container_node_)->length();
case kUserSelectContainNode:
return HasChildren() || !ChildAfterPosition();
}
NOTREACHED() << " Invalid container_type_=" << container_type_;
}
template <typename Strategy>
void FastPositionIteratorAlgorithm<Strategy>::EnsureOffsetInContainer() const {
DCHECK(container_type_ == kContainerNode ||
container_type_ == kUserSelectContainNode);
if (offset_in_container_ != kInvalidOffset)
return;
offset_in_container_ = Strategy::Index(*child_before_position_) + 1;
}
template <typename Strategy>
void FastPositionIteratorAlgorithm<Strategy>::MoveOffsetInContainerBy(
int delta) {
DCHECK(delta == 1 || delta == -1) << delta;
if (offset_in_container_ == kInvalidOffset)
return;
offset_in_container_ += delta;
}
template <typename Strategy>
void FastPositionIteratorAlgorithm<Strategy>::PopOffsetStack() {
if (offset_stack_.empty()) {
offset_in_container_ = kInvalidOffset;
return;
}
offset_in_container_ = offset_stack_.back();
offset_stack_.pop_back();
}
template <typename Strategy>
void FastPositionIteratorAlgorithm<Strategy>::PushThenSetOffset(
unsigned offset_in_container) {
offset_stack_.push_back(offset_in_container_);
offset_in_container_ = offset_in_container;
AssertOffsetInContainerIsValid();
}
template class CORE_TEMPLATE_EXPORT
FastPositionIteratorAlgorithm<EditingStrategy>;
template class CORE_TEMPLATE_EXPORT
FastPositionIteratorAlgorithm<EditingInFlatTreeStrategy>;
// ---
template <typename Strategy>
PositionIteratorAlgorithm<Strategy>::PositionIteratorAlgorithm(
const PositionTemplate<Strategy>& position)
: fast_(!RuntimeEnabledFeatures::FastPositionIteratorEnabled()
? PositionTemplate<Strategy>()
: position),
slow_(RuntimeEnabledFeatures::FastPositionIteratorEnabled()
? PositionTemplate<Strategy>()
: position) {}
template <typename Strategy>
PositionIteratorAlgorithm<Strategy>::PositionIteratorAlgorithm(
const PositionIteratorAlgorithm& other)
: fast_(other.fast_), slow_(other.slow_) {}
template <typename Strategy>
PositionIteratorAlgorithm<Strategy>&
PositionIteratorAlgorithm<Strategy>::operator=(
const PositionIteratorAlgorithm& other) {
fast_ = other.fast_;
slow_ = other.slow_;
return *this;
}
template <typename Strategy>
PositionTemplate<Strategy>
PositionIteratorAlgorithm<Strategy>::DeprecatedComputePosition() const {
if (!RuntimeEnabledFeatures::FastPositionIteratorEnabled())
return slow_.DeprecatedComputePosition();
return fast_.DeprecatedComputePosition();
}
template <typename Strategy>
PositionTemplate<Strategy>
PositionIteratorAlgorithm<Strategy>::ComputePosition() const {
if (!RuntimeEnabledFeatures::FastPositionIteratorEnabled())
return slow_.ComputePosition();
return fast_.ComputePosition();
}
template <typename Strategy>
void PositionIteratorAlgorithm<Strategy>::Decrement() {
if (!RuntimeEnabledFeatures::FastPositionIteratorEnabled())
return slow_.Decrement();
fast_.Decrement();
}
template <typename Strategy>
void PositionIteratorAlgorithm<Strategy>::Increment() {
if (!RuntimeEnabledFeatures::FastPositionIteratorEnabled())
return slow_.Increment();
fast_.Increment();
}
template <typename Strategy>
Node* PositionIteratorAlgorithm<Strategy>::GetNode() const {
if (!RuntimeEnabledFeatures::FastPositionIteratorEnabled())
return slow_.GetNode();
return fast_.GetNode();
}
template <typename Strategy>
int PositionIteratorAlgorithm<Strategy>::OffsetInTextNode() const {
if (!RuntimeEnabledFeatures::FastPositionIteratorEnabled())
return slow_.OffsetInTextNode();
return fast_.OffsetInTextNode();
}
template <typename Strategy>
bool PositionIteratorAlgorithm<Strategy>::AtStart() const {
if (!RuntimeEnabledFeatures::FastPositionIteratorEnabled())
return slow_.AtStart();
return fast_.AtStart();
}
template <typename Strategy>
bool PositionIteratorAlgorithm<Strategy>::AtEnd() const {
if (!RuntimeEnabledFeatures::FastPositionIteratorEnabled())
return slow_.AtEnd();
return fast_.AtEnd();
}
template <typename Strategy>
bool PositionIteratorAlgorithm<Strategy>::AtStartOfNode() const {
if (!RuntimeEnabledFeatures::FastPositionIteratorEnabled())
return slow_.AtStartOfNode();
return fast_.AtStartOfNode();
}
template <typename Strategy>
bool PositionIteratorAlgorithm<Strategy>::AtEndOfNode() const {
if (!RuntimeEnabledFeatures::FastPositionIteratorEnabled())
return slow_.AtEndOfNode();
return fast_.AtEndOfNode();
}
template class CORE_TEMPLATE_EXPORT PositionIteratorAlgorithm<EditingStrategy>;
template class CORE_TEMPLATE_EXPORT
PositionIteratorAlgorithm<EditingInFlatTreeStrategy>;
} // namespace blink