Bug 1728955: part 3) Add logging to `nsBaseClipboard`. r=masayuki
[gecko.git] / dom / base / RangeBoundary.h
blob0f10ee7782a73ded8ba1cc642ce10d757a30d223
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
10 #include "nsCOMPtr.h"
11 #include "nsIContent.h"
12 #include "mozilla/Assertions.h"
13 #include "mozilla/Maybe.h"
15 class nsRange;
17 namespace mozilla {
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
25 // correct child.
27 // mParent
28 // |
29 // [child0] [child1] [child2]
30 // / |
31 // mRef mOffset=2
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>>
41 RangeBoundary;
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;
54 friend nsRange;
56 friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&,
57 RangeBoundary&, const char*,
58 uint32_t);
59 friend void ImplCycleCollectionUnlink(RangeBoundary&);
61 static const uint32_t kFallbackOffset = 0;
63 public:
64 RangeBoundaryBase(nsINode* aContainer, nsIContent* aRef)
65 : mParent(aContainer), mRef(aRef) {
66 if (mRef) {
67 NS_WARNING_ASSERTION(mRef->GetParentNode() == mParent,
68 "Initializing RangeBoundary with invalid value");
69 } else {
70 mOffset.emplace(0);
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()) {
105 return nullptr;
107 if (!mRef) {
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())) {
125 return nullptr;
127 if (!mRef) {
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.
133 return nullptr;
135 return firstChild->GetNextSibling();
137 if (NS_WARN_IF(!mRef->GetNextSibling())) {
138 // Already referring the end of the container.
139 return nullptr;
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())) {
151 return nullptr;
153 if (NS_WARN_IF(!mRef)) {
154 // Already referring the start of the container.
155 return nullptr;
157 return mRef;
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()) {
173 if (!mOffset) {
174 DetermineOffsetFromReference();
177 return mOffset;
179 case OffsetFilter::kValidOrInvalidOffsets: {
180 if (mOffset.isSome()) {
181 return mOffset;
184 if (mParent) {
185 DetermineOffsetFromReference();
186 return mOffset;
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);
200 private:
201 void DetermineOffsetFromReference() const {
202 MOZ_ASSERT(mParent);
203 MOZ_ASSERT(mRef);
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() {
212 MOZ_ASSERT(mParent);
213 MOZ_ASSERT(mParent->IsContainerNode(),
214 "Range is positioned on a text node!");
216 if (!mRef) {
217 MOZ_ASSERT(mOffset.isSome() && mOffset.value() == 0,
218 "Invalidating offset of invalid RangeBoundary?");
219 return;
221 mOffset.reset();
224 public:
225 bool IsSet() const { return mParent && (mRef || mOffset.isSome()); }
227 bool IsSetAndValid() const {
228 if (!IsSet()) {
229 return false;
232 if (Ref()) {
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
257 // of RangeBoundary.
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) {
270 mRef = aOther.mRef;
272 mOffset = aOther.mOffset;
273 return *this;
276 bool Equals(const nsINode* aNode, uint32_t aOffset) const {
277 if (mParent != aNode) {
278 return false;
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);
296 private:
297 ParentType mParent;
298 RefType mRef;
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)