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