1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #ifndef mozilla_RangeBoundary_h
8 #define mozilla_RangeBoundary_h
11 #include "nsIContent.h"
12 #include "mozilla/Assertions.h"
13 #include "mozilla/Maybe.h"
19 template <typename T
, typename U
>
20 class EditorDOMPointBase
;
22 // This class will maintain a reference to the child immediately
23 // before the boundary's offset. We try to avoid computing the
24 // offset as much as possible and just ensure mRef points to the
29 // [child0] [child1] [child2]
33 // If mOffset == 0, mRef is null.
34 // For text nodes, mRef will always be null and the offset will
35 // be kept up-to-date.
37 template <typename ParentType
, typename RefType
>
38 class RangeBoundaryBase
;
40 typedef RangeBoundaryBase
<nsCOMPtr
<nsINode
>, nsCOMPtr
<nsIContent
>>
42 typedef RangeBoundaryBase
<nsINode
*, nsIContent
*> RawRangeBoundary
;
44 // This class has two specializations, one using reference counting
45 // pointers and one using raw pointers. This helps us avoid unnecessary
46 // AddRef/Release calls.
47 template <typename ParentType
, typename RefType
>
48 class RangeBoundaryBase
{
49 template <typename T
, typename U
>
50 friend class RangeBoundaryBase
;
51 template <typename T
, typename U
>
52 friend class EditorDOMPointBase
;
56 friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback
&,
57 RangeBoundary
&, const char*,
59 friend void ImplCycleCollectionUnlink(RangeBoundary
&);
61 static const uint32_t kFallbackOffset
= 0;
64 RangeBoundaryBase(nsINode
* aContainer
, nsIContent
* aRef
)
65 : mParent(aContainer
), mRef(aRef
) {
67 NS_WARNING_ASSERTION(mRef
->GetParentNode() == mParent
,
68 "Initializing RangeBoundary with invalid value");
74 RangeBoundaryBase(nsINode
* aContainer
, uint32_t aOffset
)
75 : mParent(aContainer
), mRef(nullptr), mOffset(mozilla::Some(aOffset
)) {
76 if (mParent
&& mParent
->IsContainerNode()) {
77 // Find a reference node
78 if (aOffset
== mParent
->GetChildCount()) {
79 mRef
= mParent
->GetLastChild();
80 } else if (aOffset
> 0) {
81 mRef
= mParent
->GetChildAt_Deprecated(aOffset
- 1);
84 NS_WARNING_ASSERTION(mRef
|| aOffset
== 0,
85 "Constructing RangeBoundary with invalid value");
87 NS_WARNING_ASSERTION(!mRef
|| mRef
->GetParentNode() == mParent
,
88 "Constructing RangeBoundary with invalid value");
92 RangeBoundaryBase() : mParent(nullptr), mRef(nullptr) {}
94 // Needed for initializing RawRangeBoundary from an existing RangeBoundary.
95 template <typename PT
, typename RT
>
96 explicit RangeBoundaryBase(const RangeBoundaryBase
<PT
, RT
>& aOther
)
97 : mParent(aOther
.mParent
), mRef(aOther
.mRef
), mOffset(aOther
.mOffset
) {}
99 nsIContent
* Ref() const { return mRef
; }
101 nsINode
* Container() const { return mParent
; }
103 nsIContent
* GetChildAtOffset() const {
104 if (!mParent
|| !mParent
->IsContainerNode()) {
108 MOZ_ASSERT(*Offset(OffsetFilter::kValidOrInvalidOffsets
) == 0,
109 "invalid RangeBoundary");
110 return mParent
->GetFirstChild();
112 MOZ_ASSERT(mParent
->GetChildAt_Deprecated(
113 *Offset(OffsetFilter::kValidOrInvalidOffsets
)) ==
114 mRef
->GetNextSibling());
115 return mRef
->GetNextSibling();
119 * GetNextSiblingOfChildOffset() returns next sibling of a child at offset.
120 * If this refers after the last child or the container cannot have children,
121 * this returns nullptr with warning.
123 nsIContent
* GetNextSiblingOfChildAtOffset() const {
124 if (NS_WARN_IF(!mParent
) || NS_WARN_IF(!mParent
->IsContainerNode())) {
128 MOZ_ASSERT(*Offset(OffsetFilter::kValidOffsets
) == 0,
129 "invalid RangeBoundary");
130 nsIContent
* firstChild
= mParent
->GetFirstChild();
131 if (NS_WARN_IF(!firstChild
)) {
132 // Already referring the end of the container.
135 return firstChild
->GetNextSibling();
137 if (NS_WARN_IF(!mRef
->GetNextSibling())) {
138 // Already referring the end of the container.
141 return mRef
->GetNextSibling()->GetNextSibling();
145 * GetPreviousSiblingOfChildAtOffset() returns previous sibling of a child
146 * at offset. If this refers the first child or the container cannot have
147 * children, this returns nullptr with warning.
149 nsIContent
* GetPreviousSiblingOfChildAtOffset() const {
150 if (NS_WARN_IF(!mParent
) || NS_WARN_IF(!mParent
->IsContainerNode())) {
153 if (NS_WARN_IF(!mRef
)) {
154 // Already referring the start of the container.
160 enum class OffsetFilter
{ kValidOffsets
, kValidOrInvalidOffsets
};
163 * @return maybe an offset, depending on aOffsetFilter. If it is:
164 * kValidOffsets: if the offset is valid, it, Nothing{} otherwise.
165 * kValidOrInvalidOffsets: the internally stored offset, even if
166 * invalid, or if not available, a defined
167 * default value. That is, always some value.
169 Maybe
<uint32_t> Offset(const OffsetFilter aOffsetFilter
) const {
170 switch (aOffsetFilter
) {
171 case OffsetFilter::kValidOffsets
: {
172 if (IsSetAndValid()) {
174 DetermineOffsetFromReference();
179 case OffsetFilter::kValidOrInvalidOffsets
: {
180 if (mOffset
.isSome()) {
185 DetermineOffsetFromReference();
189 return Some(kFallbackOffset
);
193 // Needed to calm the compiler. There was deliberately no default case added
194 // to the above switch-statement, because it would prevent build-errors when
195 // not all enumerators are handled.
196 MOZ_ASSERT_UNREACHABLE();
197 return Some(kFallbackOffset
);
201 void DetermineOffsetFromReference() const {
204 MOZ_ASSERT(mRef
->GetParentNode() == mParent
);
206 const int32_t index
= mParent
->ComputeIndexOf(mRef
);
207 MOZ_ASSERT(index
>= 0);
208 mOffset
.emplace(static_cast<uint32_t>(index
+ 1));
211 void InvalidateOffset() {
213 MOZ_ASSERT(mParent
->IsContainerNode(),
214 "Range is positioned on a text node!");
217 MOZ_ASSERT(mOffset
.isSome() && mOffset
.value() == 0,
218 "Invalidating offset of invalid RangeBoundary?");
225 bool IsSet() const { return mParent
&& (mRef
|| mOffset
.isSome()); }
227 bool IsSetAndValid() const {
233 return Ref()->GetParentNode() == Container();
236 MOZ_ASSERT(mOffset
.isSome());
237 return *mOffset
<= Container()->Length();
240 bool IsStartOfContainer() const {
241 // We're at the first point in the container if we don't have a reference,
242 // and our offset is 0. If we don't have a Ref, we should already have an
243 // offset, so we can just directly fetch it.
244 return !Ref() && mOffset
.value() == 0;
247 bool IsEndOfContainer() const {
248 // We're at the last point in the container if Ref is a pointer to the last
249 // child in Container(), or our Offset() is the same as the length of our
250 // container. If we don't have a Ref, then we should already have an offset,
251 // so we can just directly fetch it.
252 return Ref() ? !Ref()->GetNextSibling()
253 : mOffset
.value() == Container()->Length();
256 // Convenience methods for switching between the two types
258 RangeBoundaryBase
<nsINode
*, nsIContent
*> AsRaw() const {
259 return RangeBoundaryBase
<nsINode
*, nsIContent
*>(*this);
262 template <typename A
, typename B
>
263 RangeBoundaryBase
& operator=(const RangeBoundaryBase
<A
, B
>& aOther
) {
264 // mParent and mRef can be strong pointers, so better to try to avoid any
265 // extra AddRef/Release calls.
266 if (mParent
!= aOther
.mParent
) {
267 mParent
= aOther
.mParent
;
269 if (mRef
!= aOther
.mRef
) {
272 mOffset
= aOther
.mOffset
;
276 bool Equals(const nsINode
* aNode
, uint32_t aOffset
) const {
277 if (mParent
!= aNode
) {
281 const Maybe
<uint32_t> offset
= Offset(OffsetFilter::kValidOffsets
);
282 return offset
&& (*offset
== aOffset
);
285 template <typename A
, typename B
>
286 bool operator==(const RangeBoundaryBase
<A
, B
>& aOther
) const {
287 return mParent
== aOther
.mParent
&&
288 (mRef
? mRef
== aOther
.mRef
: mOffset
== aOther
.mOffset
);
291 template <typename A
, typename B
>
292 bool operator!=(const RangeBoundaryBase
<A
, B
>& aOther
) const {
293 return !(*this == aOther
);
300 mutable mozilla::Maybe
<uint32_t> mOffset
;
303 template <typename ParentType
, typename RefType
>
304 const uint32_t RangeBoundaryBase
<ParentType
, RefType
>::kFallbackOffset
;
306 inline void ImplCycleCollectionUnlink(RangeBoundary
& aField
) {
307 ImplCycleCollectionUnlink(aField
.mParent
);
308 ImplCycleCollectionUnlink(aField
.mRef
);
311 inline void ImplCycleCollectionTraverse(
312 nsCycleCollectionTraversalCallback
& aCallback
, RangeBoundary
& aField
,
313 const char* aName
, uint32_t aFlags
) {
314 ImplCycleCollectionTraverse(aCallback
, aField
.mParent
, "mParent", 0);
315 ImplCycleCollectionTraverse(aCallback
, aField
.mRef
, "mRef", 0);
318 } // namespace mozilla
320 #endif // defined(mozilla_RangeBoundary_h)