1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #ifndef mozilla_EditorDOMPoint_h
7 #define mozilla_EditorDOMPoint_h
8
9 #include "mozilla/Assertions.h"
10 #include "mozilla/Attributes.h"
11 #include "mozilla/RangeBoundary.h"
12 #include "mozilla/dom/Element.h"
13 #include "mozilla/dom/Text.h"
14 #include "nsAtom.h"
15 #include "nsCOMPtr.h"
16 #include "nsCRT.h"
17 #include "nsGkAtoms.h"
18 #include "nsIContent.h"
19 #include "nsINode.h"
20
21 namespace mozilla {
22
23 template <typename ParentType, typename ChildType>
24 class EditorDOMPointBase;
25
26 /**
27 * EditorDOMPoint and EditorRawDOMPoint are simple classes which refers
28 * a point in the DOM tree at creating the instance or initializing the
29 * instance with calling Set().
30 *
31 * EditorDOMPoint refers container node (and child node if it's already set)
32 * with nsCOMPtr. EditorRawDOMPoint refers them with raw pointer.
33 * So, EditorRawDOMPoint is useful when you access the nodes only before
34 * changing DOM tree since increasing refcount may appear in micro benchmark
35 * if it's in a hot path. On the other hand, if you need to refer them even
36 * after changing DOM tree, you must use EditorDOMPoint.
37 *
38 * When initializing an instance only with child node or offset, the instance
39 * starts to refer the child node or offset in the container. In this case,
40 * the other information hasn't been initialized due to performance reason.
41 * When you retrieve the other information with calling Offset() or
42 * GetChild(), the other information is computed with the current DOM tree.
43 * Therefore, e.g., in the following case, the other information may be
44 * different:
45 *
46 * EditorDOMPoint pointA(container1, childNode1);
47 * EditorDOMPoint pointB(container1, childNode1);
48 * Unused << pointA.Offset(); // The offset is computed now.
49 * container1->RemoveChild(childNode1->GetPreviousSibling());
50 * Unused << pointB.Offset(); // Now, pointB.Offset() equals pointA.Offset() - 1
51 *
52 * similarly:
53 *
54 * EditorDOMPoint pointA(container1, 5);
55 * EditorDOMPoint pointB(container1, 5);
56 * Unused << pointA.GetChild(); // The child is computed now.
57 * container1->RemoveChild(childNode1->GetFirstChild());
58 * Unused << pointB.GetChild(); // Now, pointB.GetChild() equals
59 * // pointA.GetChild()->GetPreviousSibling().
60 *
61 * So, when you initialize an instance only with one information, you need to
62 * be careful when you access the other information after changing the DOM tree.
63 * When you need to lock the child node or offset and recompute the other
64 * information with new DOM tree, you can use
65 * AutoEditorDOMPointOffsetInvalidator and AutoEditorDOMPointChildInvalidator.
66 */
67
68 typedef EditorDOMPointBase<nsCOMPtr<nsINode>, nsCOMPtr<nsIContent>>
69 EditorDOMPoint;
70 typedef EditorDOMPointBase<nsINode*, nsIContent*> EditorRawDOMPoint;
71 typedef EditorDOMPointBase<RefPtr<dom::Text>, nsIContent*> EditorDOMPointInText;
72
73 template <typename ParentType, typename ChildType>
74 class EditorDOMPointBase final {
75 typedef EditorDOMPointBase<ParentType, ChildType> SelfType;
76
77 public:
EditorDOMPointBase()78 EditorDOMPointBase()
79 : mParent(nullptr), mChild(nullptr), mIsChildInitialized(false) {}
80
81 template <typename ContainerType>
EditorDOMPointBase(ContainerType * aContainer,int32_t aOffset)82 EditorDOMPointBase(ContainerType* aContainer, int32_t aOffset)
83 : mParent(aContainer),
84 mChild(nullptr),
85 mOffset(mozilla::Some(aOffset)),
86 mIsChildInitialized(false) {
87 NS_WARNING_ASSERTION(
88 !mParent || mOffset.value() <= mParent->Length(),
89 "The offset is larger than the length of aContainer or negative");
90 if (!mParent) {
91 mOffset.reset();
92 }
93 }
94
95 template <typename ContainerType, template <typename> typename StrongPtr>
EditorDOMPointBase(const StrongPtr<ContainerType> & aContainer,int32_t aOffset)96 EditorDOMPointBase(const StrongPtr<ContainerType>& aContainer,
97 int32_t aOffset)
98 : EditorDOMPointBase(aContainer.get(), aOffset) {}
99
100 /**
101 * Different from RangeBoundary, aPointedNode should be a child node
102 * which you want to refer.
103 */
EditorDOMPointBase(nsINode * aPointedNode)104 explicit EditorDOMPointBase(nsINode* aPointedNode)
105 : mParent(aPointedNode && aPointedNode->IsContent()
106 ? aPointedNode->GetParentNode()
107 : nullptr),
108 mChild(aPointedNode && aPointedNode->IsContent()
109 ? aPointedNode->AsContent()
110 : nullptr),
111 mIsChildInitialized(false) {
112 mIsChildInitialized = aPointedNode && mChild;
113 NS_WARNING_ASSERTION(IsSet(),
114 "The child is nullptr or doesn't have its parent");
115 NS_WARNING_ASSERTION(mChild && mChild->GetParentNode() == mParent,
116 "Initializing RangeBoundary with invalid value");
117 }
118
EditorDOMPointBase(nsINode * aContainer,nsIContent * aPointedNode,int32_t aOffset)119 EditorDOMPointBase(nsINode* aContainer, nsIContent* aPointedNode,
120 int32_t aOffset)
121 : mParent(aContainer),
122 mChild(aPointedNode),
123 mOffset(mozilla::Some(aOffset)),
124 mIsChildInitialized(true) {
125 MOZ_DIAGNOSTIC_ASSERT(
126 aContainer, "This constructor shouldn't be used when pointing nowhere");
127 MOZ_ASSERT(mOffset.value() <= mParent->Length());
128 MOZ_ASSERT(mChild || mParent->Length() == mOffset.value() ||
129 !mParent->IsContainerNode());
130 MOZ_ASSERT(!mChild || mParent == mChild->GetParentNode());
131 MOZ_ASSERT(mParent->GetChildAt_Deprecated(mOffset.value()) == mChild);
132 }
133
134 template <typename PT, typename CT>
EditorDOMPointBase(const RangeBoundaryBase<PT,CT> & aOther)135 explicit EditorDOMPointBase(const RangeBoundaryBase<PT, CT>& aOther)
136 : mParent(aOther.mParent),
137 mChild(aOther.mRef ? aOther.mRef->GetNextSibling()
138 : (aOther.mParent ? aOther.mParent->GetFirstChild()
139 : nullptr)),
140 mOffset(aOther.mOffset),
141 mIsChildInitialized(aOther.mRef || (aOther.mOffset.isSome() &&
142 !aOther.mOffset.value())) {}
143
144 template <typename PT, typename CT>
EditorDOMPointBase(const EditorDOMPointBase<PT,CT> & aOther)145 MOZ_IMPLICIT EditorDOMPointBase(const EditorDOMPointBase<PT, CT>& aOther)
146 : mParent(aOther.mParent),
147 mChild(aOther.mChild),
148 mOffset(aOther.mOffset),
149 mIsChildInitialized(aOther.mIsChildInitialized) {}
150
151 /**
152 * GetContainer() returns the container node at the point.
153 * GetContainerAs*() returns the container node as specific type.
154 */
GetContainer()155 nsINode* GetContainer() const { return mParent; }
156
GetContainerAsContent()157 nsIContent* GetContainerAsContent() const {
158 return nsIContent::FromNodeOrNull(mParent);
159 }
160
ContainerAsContent()161 MOZ_NEVER_INLINE_DEBUG nsIContent* ContainerAsContent() const {
162 MOZ_ASSERT(mParent);
163 MOZ_ASSERT(mParent->IsContent());
164 return mParent->AsContent();
165 }
166
GetContainerAsElement()167 dom::Element* GetContainerAsElement() const {
168 return dom::Element::FromNodeOrNull(mParent);
169 }
170
ContainerAsElement()171 MOZ_NEVER_INLINE_DEBUG dom::Element* ContainerAsElement() const {
172 MOZ_ASSERT(mParent);
173 MOZ_ASSERT(mParent->IsElement());
174 return mParent->AsElement();
175 }
176
GetContainerAsText()177 dom::Text* GetContainerAsText() const {
178 return dom::Text::FromNodeOrNull(mParent);
179 }
180
ContainerAsText()181 MOZ_NEVER_INLINE_DEBUG dom::Text* ContainerAsText() const {
182 MOZ_ASSERT(mParent);
183 MOZ_ASSERT(IsInTextNode());
184 return mParent->AsText();
185 }
186
187 /**
188 * GetContainerParent() returns parent of the container node at the point.
189 */
GetContainerParent()190 nsINode* GetContainerParent() const {
191 return mParent ? mParent->GetParent() : nullptr;
192 }
193
GetContainerParentAsContent()194 nsIContent* GetContainerParentAsContent() const {
195 return nsIContent::FromNodeOrNull(GetContainerParent());
196 }
197
GetContainerParentAsElement()198 dom::Element* GetContainerParentAsElement() const {
199 return dom::Element::FromNodeOrNull(GetContainerParent());
200 }
201
202 /**
203 * CanContainerHaveChildren() returns true if the container node can have
204 * child nodes. Otherwise, e.g., when the container is a text node, returns
205 * false.
206 */
CanContainerHaveChildren()207 bool CanContainerHaveChildren() const {
208 return mParent && mParent->IsContainerNode();
209 }
210
211 /**
212 * IsContainerEmpty() returns true if it has no children or its text is empty.
213 */
IsContainerEmpty()214 bool IsContainerEmpty() const { return mParent && !mParent->Length(); }
215
216 /**
217 * IsInContentNode() returns true if the container is a subclass of
218 * nsIContent.
219 */
IsInContentNode()220 bool IsInContentNode() const { return mParent && mParent->IsContent(); }
221
222 /**
223 * IsInDataNode() returns true if the container node is a data node including
224 * text node.
225 */
IsInDataNode()226 bool IsInDataNode() const { return mParent && mParent->IsCharacterData(); }
227
228 /**
229 * IsInTextNode() returns true if the container node is a text node.
230 */
IsInTextNode()231 bool IsInTextNode() const { return mParent && mParent->IsText(); }
232
233 /**
234 * IsInNativeAnonymousSubtree() returns true if the container is in
235 * native anonymous subtree.
236 */
IsInNativeAnonymousSubtree()237 bool IsInNativeAnonymousSubtree() const {
238 return mParent && mParent->IsInNativeAnonymousSubtree();
239 }
240
241 /**
242 * IsContainerHTMLElement() returns true if the container node is an HTML
243 * element node and its node name is aTag.
244 */
IsContainerHTMLElement(nsAtom * aTag)245 bool IsContainerHTMLElement(nsAtom* aTag) const {
246 return mParent && mParent->IsHTMLElement(aTag);
247 }
248
249 /**
250 * IsContainerAnyOfHTMLElements() returns true if the container node is an
251 * HTML element node and its node name is one of the arguments.
252 */
253 template <typename First, typename... Args>
IsContainerAnyOfHTMLElements(First aFirst,Args...aArgs)254 bool IsContainerAnyOfHTMLElements(First aFirst, Args... aArgs) const {
255 return mParent && mParent->IsAnyOfHTMLElements(aFirst, aArgs...);
256 }
257
258 /**
259 * GetChild() returns a child node which is pointed by the instance.
260 * If mChild hasn't been initialized yet, this computes the child node
261 * from mParent and mOffset with *current* DOM tree.
262 */
GetChild()263 nsIContent* GetChild() const {
264 if (!mParent || !mParent->IsContainerNode()) {
265 return nullptr;
266 }
267 if (mIsChildInitialized) {
268 return mChild;
269 }
270 // Fix child node now.
271 const_cast<SelfType*>(this)->EnsureChild();
272 return mChild;
273 }
274
275 /**
276 * GetNextSiblingOfChild() returns next sibling of the child node.
277 * If this refers after the last child or the container cannot have children,
278 * this returns nullptr with warning.
279 * If mChild hasn't been initialized yet, this computes the child node
280 * from mParent and mOffset with *current* DOM tree.
281 */
GetNextSiblingOfChild()282 nsIContent* GetNextSiblingOfChild() const {
283 if (NS_WARN_IF(!mParent) || NS_WARN_IF(!mParent->IsContainerNode())) {
284 return nullptr;
285 }
286 if (mIsChildInitialized) {
287 return mChild ? mChild->GetNextSibling() : nullptr;
288 }
289 MOZ_ASSERT(mOffset.isSome());
290 if (NS_WARN_IF(mOffset.value() > mParent->Length())) {
291 // If this has been set only offset and now the offset is invalid,
292 // let's just return nullptr.
293 return nullptr;
294 }
295 // Fix child node now.
296 const_cast<SelfType*>(this)->EnsureChild();
297 return mChild ? mChild->GetNextSibling() : nullptr;
298 }
299
300 /**
301 * GetPreviousSiblingOfChild() returns previous sibling of a child
302 * at offset. If this refers the first child or the container cannot have
303 * children, this returns nullptr with warning.
304 * If mChild hasn't been initialized yet, this computes the child node
305 * from mParent and mOffset with *current* DOM tree.
306 */
GetPreviousSiblingOfChild()307 nsIContent* GetPreviousSiblingOfChild() const {
308 if (NS_WARN_IF(!mParent) || NS_WARN_IF(!mParent->IsContainerNode())) {
309 return nullptr;
310 }
311 if (mIsChildInitialized) {
312 return mChild ? mChild->GetPreviousSibling() : mParent->GetLastChild();
313 }
314 MOZ_ASSERT(mOffset.isSome());
315 if (NS_WARN_IF(mOffset.value() > mParent->Length())) {
316 // If this has been set only offset and now the offset is invalid,
317 // let's just return nullptr.
318 return nullptr;
319 }
320 // Fix child node now.
321 const_cast<SelfType*>(this)->EnsureChild();
322 return mChild ? mChild->GetPreviousSibling() : mParent->GetLastChild();
323 }
324
325 /**
326 * Simple accessors of the character in dom::Text so that when you call
327 * these methods, you need to guarantee that the container is a dom::Text.
328 */
Char()329 MOZ_NEVER_INLINE_DEBUG char16_t Char() const {
330 MOZ_ASSERT(IsSetAndValid());
331 MOZ_ASSERT(!IsEndOfContainer());
332 return ContainerAsText()->TextFragment().CharAt(mOffset.value());
333 }
IsCharASCIISpace()334 MOZ_NEVER_INLINE_DEBUG bool IsCharASCIISpace() const {
335 return nsCRT::IsAsciiSpace(Char());
336 }
IsCharNBSP()337 MOZ_NEVER_INLINE_DEBUG bool IsCharNBSP() const { return Char() == 0x00A0; }
338
PreviousChar()339 MOZ_NEVER_INLINE_DEBUG char16_t PreviousChar() const {
340 MOZ_ASSERT(IsSetAndValid());
341 MOZ_ASSERT(!IsStartOfContainer());
342 return ContainerAsText()->TextFragment().CharAt(mOffset.value() - 1);
343 }
IsPreviousCharASCIISpace()344 MOZ_NEVER_INLINE_DEBUG bool IsPreviousCharASCIISpace() const {
345 return nsCRT::IsAsciiSpace(PreviousChar());
346 }
IsPreviousCharNBSP()347 MOZ_NEVER_INLINE_DEBUG bool IsPreviousCharNBSP() const {
348 return PreviousChar() == 0x00A0;
349 }
350
NextChar()351 MOZ_NEVER_INLINE_DEBUG char16_t NextChar() const {
352 MOZ_ASSERT(IsSetAndValid());
353 MOZ_ASSERT(!IsAtLastContent() && !IsEndOfContainer());
354 return ContainerAsText()->TextFragment().CharAt(mOffset.value() + 1);
355 }
IsNextCharASCIISpace()356 MOZ_NEVER_INLINE_DEBUG bool IsNextCharASCIISpace() const {
357 return nsCRT::IsAsciiSpace(NextChar());
358 }
IsNextCharNBSP()359 MOZ_NEVER_INLINE_DEBUG bool IsNextCharNBSP() const {
360 return NextChar() == 0x00A0;
361 }
362
Offset()363 uint32_t Offset() const {
364 if (mOffset.isSome()) {
365 MOZ_ASSERT(mOffset.isSome());
366 return mOffset.value();
367 }
368 if (!mParent) {
369 MOZ_ASSERT(!mChild);
370 return 0;
371 }
372 MOZ_ASSERT(mParent->IsContainerNode(),
373 "If the container cannot have children, mOffset.isSome() should "
374 "be true");
375 if (!mChild) {
376 // We're referring after the last child. Fix offset now.
377 const_cast<SelfType*>(this)->mOffset = mozilla::Some(mParent->Length());
378 return mOffset.value();
379 }
380 MOZ_ASSERT(mChild->GetParentNode() == mParent);
381 // Fix offset now.
382 if (mChild == mParent->GetFirstChild()) {
383 const_cast<SelfType*>(this)->mOffset = mozilla::Some(0);
384 } else {
385 const_cast<SelfType*>(this)->mOffset =
386 mozilla::Some(mParent->ComputeIndexOf(mChild));
387 }
388 return mOffset.value();
389 }
390
391 /**
392 * Set() sets a point to aOffset or aChild.
393 * If it's set with aOffset, mChild is invalidated. If it's set with aChild,
394 * mOffset may be invalidated.
395 */
396 template <typename ContainerType>
Set(ContainerType * aContainer,int32_t aOffset)397 void Set(ContainerType* aContainer, int32_t aOffset) {
398 mParent = aContainer;
399 mChild = nullptr;
400 mOffset = mozilla::Some(aOffset);
401 mIsChildInitialized = false;
402 NS_ASSERTION(!mParent || mOffset.value() <= mParent->Length(),
403 "The offset is out of bounds");
404 }
405 template <typename ContainerType, template <typename> typename StrongPtr>
Set(const StrongPtr<ContainerType> & aContainer,int32_t aOffset)406 void Set(const StrongPtr<ContainerType>& aContainer, int32_t aOffset) {
407 Set(aContainer.get(), aOffset);
408 }
Set(const nsINode * aChild)409 void Set(const nsINode* aChild) {
410 MOZ_ASSERT(aChild);
411 if (NS_WARN_IF(!aChild->IsContent())) {
412 Clear();
413 return;
414 }
415 mParent = aChild->GetParentNode();
416 mChild = const_cast<nsIContent*>(aChild->AsContent());
417 mOffset.reset();
418 mIsChildInitialized = true;
419 }
420
421 /**
422 * SetToEndOf() sets this to the end of aContainer. Then, mChild is always
423 * nullptr but marked as initialized and mOffset is always set.
424 */
425 template <typename ContainerType>
SetToEndOf(const ContainerType * aContainer)426 MOZ_NEVER_INLINE_DEBUG void SetToEndOf(const ContainerType* aContainer) {
427 MOZ_ASSERT(aContainer);
428 mParent = const_cast<ContainerType*>(aContainer);
429 mChild = nullptr;
430 mOffset = mozilla::Some(mParent->Length());
431 mIsChildInitialized = true;
432 }
433 template <typename ContainerType, template <typename> typename StrongPtr>
SetToEndOf(const StrongPtr<ContainerType> & aContainer)434 MOZ_NEVER_INLINE_DEBUG void SetToEndOf(
435 const StrongPtr<ContainerType>& aContainer) {
436 SetToEndOf(aContainer.get());
437 }
438 template <typename ContainerType>
AtEndOf(const ContainerType & aContainer)439 MOZ_NEVER_INLINE_DEBUG static SelfType AtEndOf(
440 const ContainerType& aContainer) {
441 SelfType point;
442 point.SetToEndOf(&aContainer);
443 return point;
444 }
445 template <typename ContainerType, template <typename> typename StrongPtr>
AtEndOf(const StrongPtr<ContainerType> & aContainer)446 MOZ_NEVER_INLINE_DEBUG static SelfType AtEndOf(
447 const StrongPtr<ContainerType>& aContainer) {
448 MOZ_ASSERT(aContainer.get());
449 return AtEndOf(*aContainer.get());
450 }
451
452 /**
453 * SetAfter() sets mChild to next sibling of aChild.
454 */
SetAfter(const nsINode * aChild)455 void SetAfter(const nsINode* aChild) {
456 MOZ_ASSERT(aChild);
457 nsIContent* nextSibling = aChild->GetNextSibling();
458 if (nextSibling) {
459 Set(nextSibling);
460 return;
461 }
462 nsINode* parentNode = aChild->GetParentNode();
463 if (NS_WARN_IF(!parentNode)) {
464 Clear();
465 return;
466 }
467 SetToEndOf(parentNode);
468 }
469 template <typename ContainerType>
After(const ContainerType & aContainer)470 static SelfType After(const ContainerType& aContainer) {
471 SelfType point;
472 point.SetAfter(&aContainer);
473 return point;
474 }
475 template <typename ContainerType, template <typename> typename StrongPtr>
After(const StrongPtr<ContainerType> & aContainer)476 MOZ_NEVER_INLINE_DEBUG static SelfType After(
477 const StrongPtr<ContainerType>& aContainer) {
478 MOZ_ASSERT(aContainer.get());
479 return After(*aContainer.get());
480 }
481 template <typename PT, typename CT>
After(const EditorDOMPointBase<PT,CT> & aPoint)482 MOZ_NEVER_INLINE_DEBUG static SelfType After(
483 const EditorDOMPointBase<PT, CT>& aPoint) {
484 MOZ_ASSERT(aPoint.IsSet());
485 if (aPoint.mChild) {
486 return After(*aPoint.mChild);
487 }
488 if (NS_WARN_IF(aPoint.IsEndOfContainer())) {
489 return SelfType();
490 }
491 SelfType point(aPoint);
492 MOZ_ALWAYS_TRUE(point.AdvanceOffset());
493 return point;
494 }
495
496 /**
497 * NextPoint() and PreviousPoint() returns next/previous DOM point in
498 * the container.
499 */
NextPoint()500 MOZ_NEVER_INLINE_DEBUG SelfType NextPoint() const {
501 NS_ASSERTION(!IsEndOfContainer(), "Should not be at end of the container");
502 SelfType result(*this);
503 result.AdvanceOffset();
504 return result;
505 }
PreviousPoint()506 MOZ_NEVER_INLINE_DEBUG SelfType PreviousPoint() const {
507 NS_ASSERTION(!IsStartOfContainer(),
508 "Should not be at start of the container");
509 SelfType result(*this);
510 result.RewindOffset();
511 return result;
512 }
513
514 /**
515 * Clear() makes the instance not point anywhere.
516 */
Clear()517 void Clear() {
518 mParent = nullptr;
519 mChild = nullptr;
520 mOffset.reset();
521 mIsChildInitialized = false;
522 }
523
524 /**
525 * AdvanceOffset() tries to refer next sibling of mChild and/of next offset.
526 * If the container can have children and there is no next sibling or the
527 * offset reached the length of the container, this outputs warning and does
528 * nothing. So, callers need to check if there is next sibling which you
529 * need to refer.
530 *
531 * @return true if there is a next DOM point to refer.
532 */
AdvanceOffset()533 bool AdvanceOffset() {
534 if (NS_WARN_IF(!mParent)) {
535 return false;
536 }
537 // If only mOffset is available, just compute the offset.
538 if ((mOffset.isSome() && !mIsChildInitialized) ||
539 !mParent->IsContainerNode()) {
540 MOZ_ASSERT(mOffset.isSome());
541 MOZ_ASSERT(!mChild);
542 if (NS_WARN_IF(mOffset.value() >= mParent->Length())) {
543 // We're already referring the start of the container.
544 return false;
545 }
546 mOffset = mozilla::Some(mOffset.value() + 1);
547 return true;
548 }
549
550 MOZ_ASSERT(mIsChildInitialized);
551 MOZ_ASSERT(!mOffset.isSome() || mOffset.isSome());
552 if (NS_WARN_IF(!mParent->HasChildren()) || NS_WARN_IF(!mChild) ||
553 NS_WARN_IF(mOffset.isSome() && mOffset.value() >= mParent->Length())) {
554 // We're already referring the end of the container (or outside).
555 return false;
556 }
557
558 if (mOffset.isSome()) {
559 MOZ_ASSERT(mOffset.isSome());
560 mOffset = mozilla::Some(mOffset.value() + 1);
561 }
562 mChild = mChild->GetNextSibling();
563 return true;
564 }
565
566 /**
567 * RewindOffset() tries to refer previous sibling of mChild and/or previous
568 * offset. If the container can have children and there is no next previous
569 * or the offset is 0, this outputs warning and does nothing. So, callers
570 * need to check if there is previous sibling which you need to refer.
571 *
572 * @return true if there is a previous DOM point to refer.
573 */
RewindOffset()574 bool RewindOffset() {
575 if (NS_WARN_IF(!mParent)) {
576 return false;
577 }
578 // If only mOffset is available, just compute the offset.
579 if ((mOffset.isSome() && !mIsChildInitialized) ||
580 !mParent->IsContainerNode()) {
581 MOZ_ASSERT(mOffset.isSome());
582 MOZ_ASSERT(!mChild);
583 if (NS_WARN_IF(!mOffset.value()) ||
584 NS_WARN_IF(mOffset.value() > mParent->Length())) {
585 // We're already referring the start of the container or
586 // the offset is invalid since perhaps, the offset was set before
587 // the last DOM tree change.
588 return false;
589 }
590 mOffset = mozilla::Some(mOffset.value() - 1);
591 return true;
592 }
593
594 MOZ_ASSERT(mIsChildInitialized);
595 MOZ_ASSERT(!mOffset.isSome() || mOffset.isSome());
596 if (NS_WARN_IF(!mParent->HasChildren()) ||
597 NS_WARN_IF(mChild && !mChild->GetPreviousSibling()) ||
598 NS_WARN_IF(mOffset.isSome() && !mOffset.value())) {
599 // We're already referring the start of the container (or the child has
600 // been moved from the container?).
601 return false;
602 }
603
604 nsIContent* previousSibling =
605 mChild ? mChild->GetPreviousSibling() : mParent->GetLastChild();
606 if (NS_WARN_IF(!previousSibling)) {
607 // We're already referring the first child of the container.
608 return false;
609 }
610
611 if (mOffset.isSome()) {
612 mOffset = mozilla::Some(mOffset.value() - 1);
613 }
614 mChild = previousSibling;
615 return true;
616 }
617
618 /**
619 * GetNonAnonymousSubtreePoint() returns a DOM point which is NOT in
620 * native-anonymous subtree. If the instance isn't in native-anonymous
621 * subtree, this returns same point. Otherwise, climbs up until finding
622 * non-native-anonymous parent and returns the point of it. I.e.,
623 * container is parent of the found non-anonymous-native node.
624 */
GetNonAnonymousSubtreePoint()625 EditorRawDOMPoint GetNonAnonymousSubtreePoint() const {
626 if (NS_WARN_IF(!IsSet())) {
627 return EditorRawDOMPoint();
628 }
629 if (!IsInNativeAnonymousSubtree()) {
630 return EditorRawDOMPoint(*this);
631 }
632 nsINode* parent;
633 for (parent = mParent->GetParentNode();
634 parent && parent->IsInNativeAnonymousSubtree();
635 parent = parent->GetParentNode()) {
636 }
637 if (!parent) {
638 return EditorRawDOMPoint();
639 }
640 return EditorRawDOMPoint(parent);
641 }
642
IsSet()643 bool IsSet() const {
644 return mParent && (mIsChildInitialized || mOffset.isSome());
645 }
646
IsSetAndValid()647 bool IsSetAndValid() const {
648 if (!IsSet()) {
649 return false;
650 }
651
652 if (mChild && mChild->GetParentNode() != mParent) {
653 return false;
654 }
655 if (mOffset.isSome() && mOffset.value() > mParent->Length()) {
656 return false;
657 }
658 return true;
659 }
660
HasChildMovedFromContainer()661 bool HasChildMovedFromContainer() const {
662 return mChild && mChild->GetParentNode() != mParent;
663 }
664
IsStartOfContainer()665 bool IsStartOfContainer() const {
666 // If we're referring the first point in the container:
667 // If mParent is not a container like a text node, mOffset is 0.
668 // If mChild is initialized and it's first child of mParent.
669 // If mChild isn't initialized and the offset is 0.
670 if (NS_WARN_IF(!mParent)) {
671 return false;
672 }
673 if (!mParent->IsContainerNode()) {
674 return !mOffset.value();
675 }
676 if (mIsChildInitialized) {
677 if (mParent->GetFirstChild() == mChild) {
678 NS_WARNING_ASSERTION(!mOffset.isSome() || !mOffset.value(),
679 "If mOffset was initialized, it should be 0");
680 return true;
681 }
682 NS_WARNING_ASSERTION(!mOffset.isSome() || mParent->GetChildAt_Deprecated(
683 mOffset.value()) == mChild,
684 "mOffset and mChild are mismatched");
685 return false;
686 }
687 MOZ_ASSERT(mOffset.isSome());
688 return !mOffset.value();
689 }
690
IsEndOfContainer()691 bool IsEndOfContainer() const {
692 // If we're referring after the last point of the container:
693 // If mParent is not a container like text node, mOffset is same as the
694 // length of the container.
695 // If mChild is initialized and it's nullptr.
696 // If mChild isn't initialized and mOffset is same as the length of the
697 // container.
698 if (NS_WARN_IF(!mParent)) {
699 return false;
700 }
701 if (!mParent->IsContainerNode()) {
702 return mOffset.value() == mParent->Length();
703 }
704 if (mIsChildInitialized) {
705 if (!mChild) {
706 NS_WARNING_ASSERTION(
707 !mOffset.isSome() || mOffset.value() == mParent->Length(),
708 "If mOffset was initialized, it should be length of the container");
709 return true;
710 }
711 NS_WARNING_ASSERTION(!mOffset.isSome() || mParent->GetChildAt_Deprecated(
712 mOffset.value()) == mChild,
713 "mOffset and mChild are mismatched");
714 return false;
715 }
716 MOZ_ASSERT(mOffset.isSome());
717 return mOffset.value() == mParent->Length();
718 }
719
720 /**
721 * IsAtLastContent() returns true when it refers last child of the container
722 * or last character offset of text node.
723 */
IsAtLastContent()724 bool IsAtLastContent() const {
725 if (NS_WARN_IF(!mParent)) {
726 return false;
727 }
728 if (mParent->IsContainerNode() && mOffset.isSome()) {
729 return mOffset.value() == mParent->Length() - 1;
730 }
731 if (mIsChildInitialized) {
732 if (mChild && mChild == mParent->GetLastChild()) {
733 NS_WARNING_ASSERTION(
734 !mOffset.isSome() || mOffset.value() == mParent->Length() - 1,
735 "If mOffset was initialized, it should be length - 1 of the "
736 "container");
737 return true;
738 }
739 NS_WARNING_ASSERTION(!mOffset.isSome() || mParent->GetChildAt_Deprecated(
740 mOffset.value()) == mChild,
741 "mOffset and mChild are mismatched");
742 return false;
743 }
744 MOZ_ASSERT(mOffset.isSome());
745 return mOffset.value() == mParent->Length() - 1;
746 }
747
IsBRElementAtEndOfContainer()748 bool IsBRElementAtEndOfContainer() const {
749 if (NS_WARN_IF(!mParent)) {
750 return false;
751 }
752 if (!mParent->IsContainerNode()) {
753 return false;
754 }
755 const_cast<SelfType*>(this)->EnsureChild();
756 if (!mChild || mChild->GetNextSibling()) {
757 return false;
758 }
759 return mChild->IsHTMLElement(nsGkAtoms::br);
760 }
761
762 template <typename A, typename B>
763 EditorDOMPointBase& operator=(const RangeBoundaryBase<A, B>& aOther) {
764 mParent = aOther.mParent;
765 mChild = aOther.mRef ? aOther.mRef->GetNextSibling()
766 : (aOther.mParent && aOther.mParent->IsContainerNode()
767 ? aOther.mParent->GetFirstChild()
768 : nullptr);
769 mOffset = aOther.mOffset;
770 mIsChildInitialized =
771 aOther.mRef || (aOther.mParent && !aOther.mParent->IsContainerNode()) ||
772 (aOther.mOffset.isSome() && !aOther.mOffset.value());
773 return *this;
774 }
775
776 template <typename A, typename B>
777 EditorDOMPointBase& operator=(const EditorDOMPointBase<A, B>& aOther) {
778 mParent = aOther.mParent;
779 mChild = aOther.mChild;
780 mOffset = aOther.mOffset;
781 mIsChildInitialized = aOther.mIsChildInitialized;
782 return *this;
783 }
784
785 template <typename A, typename B>
786 bool operator==(const EditorDOMPointBase<A, B>& aOther) const {
787 if (mParent != aOther.mParent) {
788 return false;
789 }
790
791 if (mOffset.isSome() && aOther.mOffset.isSome()) {
792 // If both mOffset are set, we need to compare both mRef too because
793 // the relation of mRef and mOffset have already broken by DOM tree
794 // changes.
795 if (mOffset != aOther.mOffset) {
796 return false;
797 }
798 if (mChild == aOther.mChild) {
799 return true;
800 }
801 if (NS_WARN_IF(mIsChildInitialized && aOther.mIsChildInitialized)) {
802 // In this case, relation between mChild and mOffset of one of or both
803 // of them doesn't match with current DOM tree since the DOM tree might
804 // have been changed after computing mChild or mOffset.
805 return false;
806 }
807 // If one of mChild hasn't been computed yet, we should compare them only
808 // with mOffset. Perhaps, we shouldn't copy mChild from non-nullptr one
809 // to the other since if we copy it here, it may be unexpected behavior
810 // for some callers.
811 return true;
812 }
813
814 MOZ_ASSERT(mIsChildInitialized || aOther.mIsChildInitialized);
815
816 if (mOffset.isSome() && !mIsChildInitialized && !aOther.mOffset.isSome() &&
817 aOther.mIsChildInitialized) {
818 // If this has only mOffset and the other has only mChild, this needs to
819 // compute mChild now.
820 const_cast<SelfType*>(this)->EnsureChild();
821 return mChild == aOther.mChild;
822 }
823
824 if (!mOffset.isSome() && mIsChildInitialized && aOther.mOffset.isSome() &&
825 !aOther.mIsChildInitialized) {
826 // If this has only mChild and the other has only mOffset, the other needs
827 // to compute mChild now.
828 const_cast<EditorDOMPointBase<A, B>&>(aOther).EnsureChild();
829 return mChild == aOther.mChild;
830 }
831
832 // If mOffset of one of them hasn't been computed from mChild yet, we should
833 // compare only with mChild. Perhaps, we shouldn't copy mOffset from being
834 // some one to not being some one since if we copy it here, it may be
835 // unexpected behavior for some callers.
836 return mChild == aOther.mChild;
837 }
838
839 template <typename A, typename B>
840 bool operator==(const RangeBoundaryBase<A, B>& aOther) const {
841 // TODO: Optimize this with directly comparing with RangeBoundaryBase
842 // members.
843 return *this == SelfType(aOther);
844 }
845
846 template <typename A, typename B>
847 bool operator!=(const EditorDOMPointBase<A, B>& aOther) const {
848 return !(*this == aOther);
849 }
850
851 template <typename A, typename B>
852 bool operator!=(const RangeBoundaryBase<A, B>& aOther) const {
853 return !(*this == aOther);
854 }
855
856 /**
857 * This operator should be used if API of other modules take RawRangeBoundary,
858 * e.g., methods of Selection and nsRange.
859 */
RawRangeBoundary()860 operator const RawRangeBoundary() const { return ToRawRangeBoundary(); }
ToRawRangeBoundary()861 const RawRangeBoundary ToRawRangeBoundary() const {
862 if (!IsSet() || NS_WARN_IF(!mIsChildInitialized && !mOffset.isSome())) {
863 return RawRangeBoundary();
864 }
865 if (!mParent->IsContainerNode()) {
866 MOZ_ASSERT(mOffset.value() <= mParent->Length());
867 // If the container is a data node like a text node, we need to create
868 // RangeBoundaryBase instance only with mOffset because mChild is always
869 // nullptr.
870 return RawRangeBoundary(mParent, mOffset.value());
871 }
872 if (mIsChildInitialized && mOffset.isSome()) {
873 // If we've already set both child and offset, we should create
874 // RangeBoundary with offset after validation.
875 #ifdef DEBUG
876 if (mChild) {
877 MOZ_ASSERT(mParent == mChild->GetParentNode());
878 MOZ_ASSERT(mParent->GetChildAt_Deprecated(mOffset.value()) == mChild);
879 } else {
880 MOZ_ASSERT(mParent->Length() == mOffset.value());
881 }
882 #endif // #ifdef DEBUG
883 return RawRangeBoundary(mParent, mOffset.value());
884 }
885 // Otherwise, we should create RangeBoundaryBase only with available
886 // information.
887 if (mOffset.isSome()) {
888 return RawRangeBoundary(mParent, mOffset.value());
889 }
890 if (mChild) {
891 return RawRangeBoundary(mParent, mChild->GetPreviousSibling());
892 }
893 return RawRangeBoundary(mParent, mParent->GetLastChild());
894 }
895
896 private:
EnsureChild()897 void EnsureChild() {
898 if (mIsChildInitialized) {
899 return;
900 }
901 if (!mParent) {
902 MOZ_ASSERT(!mOffset.isSome());
903 return;
904 }
905 MOZ_ASSERT(mOffset.isSome());
906 MOZ_ASSERT(mOffset.value() <= mParent->Length());
907 mIsChildInitialized = true;
908 if (!mParent->IsContainerNode()) {
909 return;
910 }
911 mChild = mParent->GetChildAt_Deprecated(mOffset.value());
912 MOZ_ASSERT(mChild || mOffset.value() == mParent->Length());
913 }
914
915 ParentType mParent;
916 ChildType mChild;
917
918 mozilla::Maybe<uint32_t> mOffset;
919
920 bool mIsChildInitialized;
921
922 template <typename PT, typename CT>
923 friend class EditorDOMPointBase;
924
925 friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&,
926 EditorDOMPoint&, const char*,
927 uint32_t);
928 friend void ImplCycleCollectionUnlink(EditorDOMPoint&);
929 };
930
ImplCycleCollectionUnlink(EditorDOMPoint & aField)931 inline void ImplCycleCollectionUnlink(EditorDOMPoint& aField) {
932 ImplCycleCollectionUnlink(aField.mParent);
933 ImplCycleCollectionUnlink(aField.mChild);
934 }
935
ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback & aCallback,EditorDOMPoint & aField,const char * aName,uint32_t aFlags)936 inline void ImplCycleCollectionTraverse(
937 nsCycleCollectionTraversalCallback& aCallback, EditorDOMPoint& aField,
938 const char* aName, uint32_t aFlags) {
939 ImplCycleCollectionTraverse(aCallback, aField.mParent, "mParent", 0);
940 ImplCycleCollectionTraverse(aCallback, aField.mChild, "mChild", 0);
941 }
942
943 /**
944 * AutoEditorDOMPointOffsetInvalidator is useful if DOM tree will be changed
945 * when EditorDOMPoint instance is available and keeps referring same child
946 * node.
947 *
948 * This class automatically guarantees that given EditorDOMPoint instance
949 * stores the child node and invalidates its offset when the instance is
950 * destroyed. Additionally, users of this class can invalidate the offset
951 * manually when they need.
952 */
953 class MOZ_STACK_CLASS AutoEditorDOMPointOffsetInvalidator final {
954 public:
AutoEditorDOMPointOffsetInvalidator(EditorDOMPoint & aPoint)955 explicit AutoEditorDOMPointOffsetInvalidator(EditorDOMPoint& aPoint)
956 : mPoint(aPoint), mCanceled(false) {
957 MOZ_ASSERT(aPoint.IsSetAndValid());
958 MOZ_ASSERT(mPoint.CanContainerHaveChildren());
959 mChild = mPoint.GetChild();
960 }
961
~AutoEditorDOMPointOffsetInvalidator()962 ~AutoEditorDOMPointOffsetInvalidator() {
963 if (!mCanceled) {
964 InvalidateOffset();
965 }
966 }
967
968 /**
969 * Manually, invalidate offset of the given point.
970 */
InvalidateOffset()971 void InvalidateOffset() {
972 if (mChild) {
973 mPoint.Set(mChild);
974 } else {
975 // If the point referred after the last child, let's keep referring
976 // after current last node of the old container.
977 mPoint.SetToEndOf(mPoint.GetContainer());
978 }
979 }
980
981 /**
982 * After calling Cancel(), mPoint won't be modified by the destructor.
983 */
Cancel()984 void Cancel() { mCanceled = true; }
985
986 private:
987 EditorDOMPoint& mPoint;
988 // Needs to store child node by ourselves because EditorDOMPoint stores
989 // child node with mRef which is previous sibling of current child node.
990 // Therefore, we cannot keep referring it if it's first child.
991 nsCOMPtr<nsIContent> mChild;
992
993 bool mCanceled;
994
995 AutoEditorDOMPointOffsetInvalidator() = delete;
996 AutoEditorDOMPointOffsetInvalidator(
997 const AutoEditorDOMPointOffsetInvalidator& aOther) = delete;
998 const AutoEditorDOMPointOffsetInvalidator& operator=(
999 const AutoEditorDOMPointOffsetInvalidator& aOther) = delete;
1000 };
1001
1002 /**
1003 * AutoEditorDOMPointChildInvalidator is useful if DOM tree will be changed
1004 * when EditorDOMPoint instance is available and keeps referring same container
1005 * and offset in it.
1006 *
1007 * This class automatically guarantees that given EditorDOMPoint instance
1008 * stores offset and invalidates its child node when the instance is destroyed.
1009 * Additionally, users of this class can invalidate the child manually when
1010 * they need.
1011 */
1012 class MOZ_STACK_CLASS AutoEditorDOMPointChildInvalidator final {
1013 public:
AutoEditorDOMPointChildInvalidator(EditorDOMPoint & aPoint)1014 explicit AutoEditorDOMPointChildInvalidator(EditorDOMPoint& aPoint)
1015 : mPoint(aPoint), mCanceled(false) {
1016 MOZ_ASSERT(aPoint.IsSetAndValid());
1017 Unused << mPoint.Offset();
1018 }
1019
~AutoEditorDOMPointChildInvalidator()1020 ~AutoEditorDOMPointChildInvalidator() {
1021 if (!mCanceled) {
1022 InvalidateChild();
1023 }
1024 }
1025
1026 /**
1027 * Manually, invalidate child of the given point.
1028 */
InvalidateChild()1029 void InvalidateChild() { mPoint.Set(mPoint.GetContainer(), mPoint.Offset()); }
1030
1031 /**
1032 * After calling Cancel(), mPoint won't be modified by the destructor.
1033 */
Cancel()1034 void Cancel() { mCanceled = true; }
1035
1036 private:
1037 EditorDOMPoint& mPoint;
1038
1039 bool mCanceled;
1040
1041 AutoEditorDOMPointChildInvalidator() = delete;
1042 AutoEditorDOMPointChildInvalidator(
1043 const AutoEditorDOMPointChildInvalidator& aOther) = delete;
1044 const AutoEditorDOMPointChildInvalidator& operator=(
1045 const AutoEditorDOMPointChildInvalidator& aOther) = delete;
1046 };
1047
1048 } // namespace mozilla
1049
1050 #endif // #ifndef mozilla_EditorDOMPoint_h
1051