blob: f36799cd3ff31213865e80b7654dace9efe4cb20 [file] [log] [blame]
/*
* Copyright (C) 2004, 2006, 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 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.
*/
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_EDITING_POSITION_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_EDITING_POSITION_H_
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/editing/editing_strategy.h"
#include "third_party/blink/renderer/platform/heap/handle.h"
#include "third_party/blink/renderer/platform/wtf/assertions.h"
namespace blink {
class Node;
class TreeScope;
enum class PositionAnchorType : unsigned {
kOffsetInAnchor,
kBeforeAnchor,
kAfterAnchor,
kBeforeChildren,
kAfterChildren,
};
// Instances of |PositionTemplate<Strategy>| are immutable.
// TODO(editing-dev): Make constructor of |PositionTemplate| take |const Node*|.
template <typename Strategy>
class PositionTemplate {
DISALLOW_NEW();
public:
PositionTemplate()
: offset_(0), anchor_type_(PositionAnchorType::kOffsetInAnchor) {}
static const TreeScope* CommonAncestorTreeScope(
const PositionTemplate<Strategy>&,
const PositionTemplate<Strategy>& b);
static PositionTemplate<Strategy> EditingPositionOf(const Node* anchor_node,
int offset);
// For creating before/after positions:
PositionTemplate(const Node* anchor_node, PositionAnchorType);
// For creating offset positions:
PositionTemplate(const Node& anchor_node, int offset);
// TODO(editing-dev): We should not pass |nullptr| as |anchor_node| for
// |Position| constructor.
// TODO(editing-dev): This constructor should eventually go away. See bug
// http://wkb.ug/63040.
PositionTemplate(const Node* anchor_node, int offset);
PositionTemplate(const PositionTemplate&);
// Returns a newly created |Position| with |kOffsetInAnchor|. |offset| can be
// out of bound. Out of bound position is used for computing undo/redo
// selection for merging text typing.
static PositionTemplate<Strategy> CreateWithoutValidation(
const Node& container,
int offset);
// TODO(editing-dev): Once we get a reason to use out of bound position,
// we should change caller to use |CreateWithoutValidation()|.
static PositionTemplate<Strategy> CreateWithoutValidationDeprecated(
const Node& container,
int offset);
explicit operator bool() const { return IsNotNull(); }
PositionAnchorType AnchorType() const { return anchor_type_; }
bool IsAfterAnchor() const {
return anchor_type_ == PositionAnchorType::kAfterAnchor;
}
bool IsAfterChildren() const {
return anchor_type_ == PositionAnchorType::kAfterChildren;
}
bool IsBeforeAnchor() const {
return anchor_type_ == PositionAnchorType::kBeforeAnchor;
}
bool IsBeforeChildren() const {
return anchor_type_ == PositionAnchorType::kBeforeChildren;
}
bool IsOffsetInAnchor() const {
return anchor_type_ == PositionAnchorType::kOffsetInAnchor;
}
// These are always DOM compliant values. Editing positions like [img, 0]
// (aka [img, before]) will return img->parentNode() and img->nodeIndex() from
// these functions.
// null for a before/after position anchored to a node with no parent
Node* ComputeContainerNode() const;
// O(n) for before/after-anchored positions, O(1) for parent-anchored
// positions
int ComputeOffsetInContainerNode() const;
// Convenience method for DOM positions that also fixes up some positions for
// editing
PositionTemplate<Strategy> ParentAnchoredEquivalent() const;
// Returns |PositionIsAnchor| type |Position| which is compatible with
// |RangeBoundaryPoint| as safe to pass |Range| constructor. Return value
// of this function is different from |parentAnchoredEquivalent()| which
// returns editing specific position.
PositionTemplate<Strategy> ToOffsetInAnchor() const;
// Inline O(1) access for Positions which callers know to be parent-anchored
int OffsetInContainerNode() const {
DCHECK(IsOffsetInAnchor());
return offset_;
}
// Returns an offset for editing based on anchor type for using with
// |AnchorNode()| function:
// - kOffsetInAnchor offset_
// - kBeforeChildren 0
// - kBeforeAnchor 0
// - kAfterChildren last editing offset in anchor node
// - kAfterAnchor last editing offset in anchor node
// Editing operations will change in anchor node rather than nodes around
// anchor node.
int ComputeEditingOffset() const;
// These are convenience methods which are smart about whether the position is
// neighbor anchored or parent anchored
Node* ComputeNodeBeforePosition() const;
Node* ComputeNodeAfterPosition() const;
// Returns node as |Range::firstNode()|. This position must be a
// |PositionAnchorType::OffsetInAhcor| to behave as |Range| boundary point.
Node* NodeAsRangeFirstNode() const;
// Similar to |nodeAsRangeLastNode()|, but returns a node in a range.
Node* NodeAsRangeLastNode() const;
// Returns a node as past last as same as |Range::pastLastNode()|. This
// function is supposed to used in HTML serialization and plain text
// iterator. This position must be a |PositionAnchorType::OffsetInAhcor| to
// behave as |Range| boundary point.
Node* NodeAsRangePastLastNode() const;
Node* CommonAncestorContainer(const PositionTemplate<Strategy>&) const;
Node* AnchorNode() const { return anchor_node_.Get(); }
Document* GetDocument() const {
return anchor_node_ ? &anchor_node_->GetDocument() : nullptr;
}
// For PositionInFlatTree, it requires an ancestor traversal to compute the
// value of IsConnected(), which can be expensive.
// TODO(crbug.com/761173): Rename to |ComputeIsConnected()| to indicate the
// cost.
bool IsConnected() const;
bool IsValidFor(const Document&) const;
bool IsNull() const { return !anchor_node_; }
bool IsNotNull() const { return anchor_node_; }
bool IsOrphan() const { return anchor_node_ && !IsConnected(); }
// Note: Comparison of positions require both parameters are non-null. You
// should check null-position before comparing them.
// TODO(yosin): We should use |Position::operator<()| instead of
// |Position::comapreTo()| to utilize |DHCECK_XX()|.
int CompareTo(const PositionTemplate<Strategy>&) const;
bool operator<(const PositionTemplate<Strategy>&) const;
bool operator<=(const PositionTemplate<Strategy>&) const;
bool operator>(const PositionTemplate<Strategy>&) const;
bool operator>=(const PositionTemplate<Strategy>&) const;
bool IsEquivalent(const PositionTemplate<Strategy>&) const;
// These can be either inside or just before/after the node, depending on
// if the node is ignored by editing or not.
// FIXME: These should go away. They only make sense for legacy positions.
bool AtFirstEditingPositionForNode() const;
bool AtLastEditingPositionForNode() const;
bool AtStartOfTree() const;
bool AtEndOfTree() const;
static PositionTemplate<Strategy> BeforeNode(const Node& anchor_node);
static PositionTemplate<Strategy> AfterNode(const Node& anchor_node);
static PositionTemplate<Strategy> InParentBeforeNode(const Node& anchor_node);
static PositionTemplate<Strategy> InParentAfterNode(const Node& anchor_node);
static int LastOffsetInNode(const Node& anchor_node);
static PositionTemplate<Strategy> FirstPositionInNode(
const Node& anchor_node);
static PositionTemplate<Strategy> LastPositionInNode(const Node& anchor_node);
static PositionTemplate<Strategy> FirstPositionInOrBeforeNode(
const Node& anchor_node);
static PositionTemplate<Strategy> LastPositionInOrAfterNode(
const Node& anchor_node);
String ToAnchorTypeAndOffsetString() const;
#ifndef NDEBUG
void ShowTreeForThis() const;
void ShowTreeForThisInFlatTree() const;
#endif
void Trace(blink::Visitor*);
private:
bool IsAfterAnchorOrAfterChildren() const {
return IsAfterAnchor() || IsAfterChildren();
}
// TODO(editing-dev): Since we should consider |Position| is constant in
// tree, we should use |Member<const Node>|. see http://crbug.com/735327
Member<Node> anchor_node_;
// offset_ can be the offset inside anchor_node_, or if
// EditingIgnoresContent(anchor_node_) returns true, then other places in
// editing will treat offset_ == 0 as "before the anchor" and offset_ > 0 as
// "after the anchor node". See ParentAnchoredEquivalent for more info.
int offset_;
PositionAnchorType anchor_type_;
};
extern template class CORE_EXTERN_TEMPLATE_EXPORT
PositionTemplate<EditingStrategy>;
extern template class CORE_EXTERN_TEMPLATE_EXPORT
PositionTemplate<EditingInFlatTreeStrategy>;
using Position = PositionTemplate<EditingStrategy>;
using PositionInFlatTree = PositionTemplate<EditingInFlatTreeStrategy>;
template <typename Strategy>
bool operator==(const PositionTemplate<Strategy>& a,
const PositionTemplate<Strategy>& b) {
if (a.IsNull())
return b.IsNull();
if (a.AnchorNode() != b.AnchorNode() || a.AnchorType() != b.AnchorType())
return false;
if (!a.IsOffsetInAnchor()) {
// Note: |m_offset| only has meaning when
// |PositionAnchorType::OffsetInAnchor|.
return true;
}
// FIXME: In <div><img></div> [div, 0] != [img, 0] even though most of the
// editing code will treat them as identical.
return a.OffsetInContainerNode() == b.OffsetInContainerNode();
}
template <typename Strategy>
bool operator!=(const PositionTemplate<Strategy>& a,
const PositionTemplate<Strategy>& b) {
return !(a == b);
}
CORE_EXPORT PositionInFlatTree ToPositionInFlatTree(const Position&);
CORE_EXPORT Position ToPositionInDOMTree(const Position&);
CORE_EXPORT Position ToPositionInDOMTree(const PositionInFlatTree&);
template <typename Strategy>
PositionTemplate<Strategy> FromPositionInDOMTree(const Position&);
template <>
inline Position FromPositionInDOMTree<EditingStrategy>(
const Position& position) {
return position;
}
template <>
inline PositionInFlatTree FromPositionInDOMTree<EditingInFlatTreeStrategy>(
const Position& position) {
return ToPositionInFlatTree(position);
}
CORE_EXPORT std::ostream& operator<<(std::ostream&, PositionAnchorType);
CORE_EXPORT std::ostream& operator<<(std::ostream&, const Position&);
CORE_EXPORT std::ostream& operator<<(std::ostream&, const PositionInFlatTree&);
} // namespace blink
#ifndef NDEBUG
// Outside the WebCore namespace for ease of invocation from gdb.
void showTree(const blink::Position&);
void showTree(const blink::Position*);
#endif
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_EDITING_POSITION_H_