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/. */
6 #ifndef mozilla_EditorDOMPoint_h
7 #define mozilla_EditorDOMPoint_h
9 #include "mozilla/Assertions.h"
10 #include "mozilla/Attributes.h"
11 #include "mozilla/Maybe.h"
12 #include "mozilla/RangeBoundary.h"
13 #include "mozilla/dom/AbstractRange.h"
14 #include "mozilla/dom/Element.h"
15 #include "mozilla/dom/Text.h"
18 #include "nsContentUtils.h"
20 #include "nsGkAtoms.h"
21 #include "nsIContent.h"
23 #include "nsStyledElement.h"
27 template <typename ParentType
, typename ChildType
>
28 class EditorDOMPointBase
;
31 * EditorDOMPoint and EditorRawDOMPoint are simple classes which refers
32 * a point in the DOM tree at creating the instance or initializing the
33 * instance with calling Set().
35 * EditorDOMPoint refers container node (and child node if it's already set)
36 * with nsCOMPtr. EditorRawDOMPoint refers them with raw pointer.
37 * So, EditorRawDOMPoint is useful when you access the nodes only before
38 * changing DOM tree since increasing refcount may appear in micro benchmark
39 * if it's in a hot path. On the other hand, if you need to refer them even
40 * after changing DOM tree, you must use EditorDOMPoint.
42 * When initializing an instance only with child node or offset, the instance
43 * starts to refer the child node or offset in the container. In this case,
44 * the other information hasn't been initialized due to performance reason.
45 * When you retrieve the other information with calling Offset() or
46 * GetChild(), the other information is computed with the current DOM tree.
47 * Therefore, e.g., in the following case, the other information may be
50 * EditorDOMPoint pointA(container1, childNode1);
51 * EditorDOMPoint pointB(container1, childNode1);
52 * Unused << pointA.Offset(); // The offset is computed now.
53 * container1->RemoveChild(childNode1->GetPreviousSibling());
54 * Unused << pointB.Offset(); // Now, pointB.Offset() equals pointA.Offset() - 1
58 * EditorDOMPoint pointA(container1, 5);
59 * EditorDOMPoint pointB(container1, 5);
60 * Unused << pointA.GetChild(); // The child is computed now.
61 * container1->RemoveChild(childNode1->GetFirstChild());
62 * Unused << pointB.GetChild(); // Now, pointB.GetChild() equals
63 * // pointA.GetChild()->GetPreviousSibling().
65 * So, when you initialize an instance only with one information, you need to
66 * be careful when you access the other information after changing the DOM tree.
67 * When you need to lock the child node or offset and recompute the other
68 * information with new DOM tree, you can use
69 * AutoEditorDOMPointOffsetInvalidator and AutoEditorDOMPointChildInvalidator.
72 typedef EditorDOMPointBase
<nsCOMPtr
<nsINode
>, nsCOMPtr
<nsIContent
>>
74 typedef EditorDOMPointBase
<nsINode
*, nsIContent
*> EditorRawDOMPoint
;
75 typedef EditorDOMPointBase
<RefPtr
<dom::Text
>, nsIContent
*> EditorDOMPointInText
;
76 typedef EditorDOMPointBase
<dom::Text
*, nsIContent
*> EditorRawDOMPointInText
;
78 template <typename ParentType
, typename ChildType
>
79 class EditorDOMPointBase final
{
80 typedef EditorDOMPointBase
<ParentType
, ChildType
> SelfType
;
84 : mParent(nullptr), mChild(nullptr), mIsChildInitialized(false) {}
86 template <typename ContainerType
>
87 EditorDOMPointBase(ContainerType
* aContainer
, int32_t aOffset
)
88 : mParent(aContainer
),
90 mOffset(mozilla::Some(aOffset
)),
91 mIsChildInitialized(false) {
93 !mParent
|| mOffset
.value() <= mParent
->Length(),
94 "The offset is larger than the length of aContainer or negative");
100 template <typename ContainerType
, template <typename
> typename StrongPtr
>
101 EditorDOMPointBase(const StrongPtr
<ContainerType
>& aContainer
,
103 : EditorDOMPointBase(aContainer
.get(), aOffset
) {}
106 * Different from RangeBoundary, aPointedNode should be a child node
107 * which you want to refer.
109 explicit EditorDOMPointBase(nsINode
* aPointedNode
)
110 : mParent(aPointedNode
&& aPointedNode
->IsContent()
111 ? aPointedNode
->GetParentNode()
113 mChild(aPointedNode
&& aPointedNode
->IsContent()
114 ? aPointedNode
->AsContent()
116 mIsChildInitialized(false) {
117 mIsChildInitialized
= aPointedNode
&& mChild
;
118 NS_WARNING_ASSERTION(IsSet(),
119 "The child is nullptr or doesn't have its parent");
120 NS_WARNING_ASSERTION(mChild
&& mChild
->GetParentNode() == mParent
,
121 "Initializing RangeBoundary with invalid value");
124 EditorDOMPointBase(nsINode
* aContainer
, nsIContent
* aPointedNode
,
126 : mParent(aContainer
),
127 mChild(aPointedNode
),
128 mOffset(mozilla::Some(aOffset
)),
129 mIsChildInitialized(true) {
130 MOZ_DIAGNOSTIC_ASSERT(
131 aContainer
, "This constructor shouldn't be used when pointing nowhere");
132 MOZ_ASSERT(mOffset
.value() <= mParent
->Length());
133 MOZ_ASSERT(mChild
|| mParent
->Length() == mOffset
.value() ||
134 !mParent
->IsContainerNode());
135 MOZ_ASSERT(!mChild
|| mParent
== mChild
->GetParentNode());
136 MOZ_ASSERT(mParent
->GetChildAt_Deprecated(mOffset
.value()) == mChild
);
139 template <typename PT
, typename CT
>
140 explicit EditorDOMPointBase(const RangeBoundaryBase
<PT
, CT
>& aOther
)
141 : mParent(aOther
.mParent
),
142 mChild(aOther
.mRef
? aOther
.mRef
->GetNextSibling()
143 : (aOther
.mParent
? aOther
.mParent
->GetFirstChild()
145 mOffset(aOther
.mOffset
),
146 mIsChildInitialized(aOther
.mRef
|| (aOther
.mOffset
.isSome() &&
147 !aOther
.mOffset
.value())) {}
149 template <typename PT
, typename CT
>
150 MOZ_IMPLICIT
EditorDOMPointBase(const EditorDOMPointBase
<PT
, CT
>& aOther
)
151 : mParent(aOther
.mParent
),
152 mChild(aOther
.mChild
),
153 mOffset(aOther
.mOffset
),
154 mIsChildInitialized(aOther
.mIsChildInitialized
) {}
157 * GetContainer() returns the container node at the point.
158 * GetContainerAs*() returns the container node as specific type.
160 nsINode
* GetContainer() const { return mParent
; }
162 nsIContent
* GetContainerAsContent() const {
163 return nsIContent::FromNodeOrNull(mParent
);
166 MOZ_NEVER_INLINE_DEBUG nsIContent
* ContainerAsContent() const {
168 MOZ_ASSERT(mParent
->IsContent());
169 return mParent
->AsContent();
172 dom::Element
* GetContainerAsElement() const {
173 return dom::Element::FromNodeOrNull(mParent
);
176 MOZ_NEVER_INLINE_DEBUG
dom::Element
* ContainerAsElement() const {
178 MOZ_ASSERT(mParent
->IsElement());
179 return mParent
->AsElement();
182 nsStyledElement
* GetContainerAsStyledElement() const {
183 return nsStyledElement::FromNodeOrNull(mParent
);
186 dom::Text
* GetContainerAsText() const {
187 return dom::Text::FromNodeOrNull(mParent
);
190 MOZ_NEVER_INLINE_DEBUG
dom::Text
* ContainerAsText() const {
192 MOZ_ASSERT(IsInTextNode());
193 return mParent
->AsText();
197 * GetContainerParent() returns parent of the container node at the point.
199 nsINode
* GetContainerParent() const {
200 return mParent
? mParent
->GetParent() : nullptr;
203 nsIContent
* GetContainerParentAsContent() const {
204 return nsIContent::FromNodeOrNull(GetContainerParent());
207 dom::Element
* GetContainerParentAsElement() const {
208 return dom::Element::FromNodeOrNull(GetContainerParent());
212 * CanContainerHaveChildren() returns true if the container node can have
213 * child nodes. Otherwise, e.g., when the container is a text node, returns
216 bool CanContainerHaveChildren() const {
217 return mParent
&& mParent
->IsContainerNode();
221 * IsContainerEmpty() returns true if it has no children or its text is empty.
223 bool IsContainerEmpty() const { return mParent
&& !mParent
->Length(); }
226 * IsInContentNode() returns true if the container is a subclass of
229 bool IsInContentNode() const { return mParent
&& mParent
->IsContent(); }
232 * IsInDataNode() returns true if the container node is a data node including
235 bool IsInDataNode() const { return mParent
&& mParent
->IsCharacterData(); }
238 * IsInTextNode() returns true if the container node is a text node.
240 bool IsInTextNode() const { return mParent
&& mParent
->IsText(); }
243 * IsInNativeAnonymousSubtree() returns true if the container is in
244 * native anonymous subtree.
246 bool IsInNativeAnonymousSubtree() const {
247 return mParent
&& mParent
->IsInNativeAnonymousSubtree();
251 * IsContainerHTMLElement() returns true if the container node is an HTML
252 * element node and its node name is aTag.
254 bool IsContainerHTMLElement(nsAtom
* aTag
) const {
255 return mParent
&& mParent
->IsHTMLElement(aTag
);
259 * IsContainerAnyOfHTMLElements() returns true if the container node is an
260 * HTML element node and its node name is one of the arguments.
262 template <typename First
, typename
... Args
>
263 bool IsContainerAnyOfHTMLElements(First aFirst
, Args
... aArgs
) const {
264 return mParent
&& mParent
->IsAnyOfHTMLElements(aFirst
, aArgs
...);
268 * GetChild() returns a child node which is pointed by the instance.
269 * If mChild hasn't been initialized yet, this computes the child node
270 * from mParent and mOffset with *current* DOM tree.
272 nsIContent
* GetChild() const {
273 if (!mParent
|| !mParent
->IsContainerNode()) {
276 if (mIsChildInitialized
) {
279 // Fix child node now.
280 const_cast<SelfType
*>(this)->EnsureChild();
285 * GetNextSiblingOfChild() returns next sibling of the child node.
286 * If this refers after the last child or the container cannot have children,
287 * this returns nullptr with warning.
288 * If mChild hasn't been initialized yet, this computes the child node
289 * from mParent and mOffset with *current* DOM tree.
291 nsIContent
* GetNextSiblingOfChild() const {
292 if (NS_WARN_IF(!mParent
) || NS_WARN_IF(!mParent
->IsContainerNode())) {
295 if (mIsChildInitialized
) {
296 return mChild
? mChild
->GetNextSibling() : nullptr;
298 MOZ_ASSERT(mOffset
.isSome());
299 if (NS_WARN_IF(mOffset
.value() > mParent
->Length())) {
300 // If this has been set only offset and now the offset is invalid,
301 // let's just return nullptr.
304 // Fix child node now.
305 const_cast<SelfType
*>(this)->EnsureChild();
306 return mChild
? mChild
->GetNextSibling() : nullptr;
310 * GetPreviousSiblingOfChild() returns previous sibling of a child
311 * at offset. If this refers the first child or the container cannot have
312 * children, this returns nullptr with warning.
313 * If mChild hasn't been initialized yet, this computes the child node
314 * from mParent and mOffset with *current* DOM tree.
316 nsIContent
* GetPreviousSiblingOfChild() const {
317 if (NS_WARN_IF(!mParent
) || NS_WARN_IF(!mParent
->IsContainerNode())) {
320 if (mIsChildInitialized
) {
321 return mChild
? mChild
->GetPreviousSibling() : mParent
->GetLastChild();
323 MOZ_ASSERT(mOffset
.isSome());
324 if (NS_WARN_IF(mOffset
.value() > mParent
->Length())) {
325 // If this has been set only offset and now the offset is invalid,
326 // let's just return nullptr.
329 // Fix child node now.
330 const_cast<SelfType
*>(this)->EnsureChild();
331 return mChild
? mChild
->GetPreviousSibling() : mParent
->GetLastChild();
335 * Simple accessors of the character in dom::Text so that when you call
336 * these methods, you need to guarantee that the container is a dom::Text.
338 MOZ_NEVER_INLINE_DEBUG char16_t
Char() const {
339 MOZ_ASSERT(IsSetAndValid());
340 MOZ_ASSERT(!IsEndOfContainer());
341 return ContainerAsText()->TextFragment().CharAt(mOffset
.value());
343 MOZ_NEVER_INLINE_DEBUG
bool IsCharASCIISpace() const {
344 return nsCRT::IsAsciiSpace(Char());
346 MOZ_NEVER_INLINE_DEBUG
bool IsCharNBSP() const { return Char() == 0x00A0; }
347 MOZ_NEVER_INLINE_DEBUG
bool IsCharASCIISpaceOrNBSP() const {
348 char16_t ch
= Char();
349 return nsCRT::IsAsciiSpace(ch
) || ch
== 0x00A0;
352 MOZ_NEVER_INLINE_DEBUG
bool IsCharHighSurrogateFollowedByLowSurrogate()
354 MOZ_ASSERT(IsSetAndValid());
355 MOZ_ASSERT(!IsEndOfContainer());
356 return ContainerAsText()
358 .IsHighSurrogateFollowedByLowSurrogateAt(mOffset
.value());
360 MOZ_NEVER_INLINE_DEBUG
bool IsCharLowSurrogateFollowingHighSurrogate() const {
361 MOZ_ASSERT(IsSetAndValid());
362 MOZ_ASSERT(!IsEndOfContainer());
363 return ContainerAsText()
365 .IsLowSurrogateFollowingHighSurrogateAt(mOffset
.value());
368 MOZ_NEVER_INLINE_DEBUG char16_t
PreviousChar() const {
369 MOZ_ASSERT(IsSetAndValid());
370 MOZ_ASSERT(!IsStartOfContainer());
371 return ContainerAsText()->TextFragment().CharAt(mOffset
.value() - 1);
373 MOZ_NEVER_INLINE_DEBUG
bool IsPreviousCharASCIISpace() const {
374 return nsCRT::IsAsciiSpace(PreviousChar());
376 MOZ_NEVER_INLINE_DEBUG
bool IsPreviousCharNBSP() const {
377 return PreviousChar() == 0x00A0;
379 MOZ_NEVER_INLINE_DEBUG
bool IsPreviousCharASCIISpaceOrNBSP() const {
380 char16_t ch
= PreviousChar();
381 return nsCRT::IsAsciiSpace(ch
) || ch
== 0x00A0;
384 MOZ_NEVER_INLINE_DEBUG char16_t
NextChar() const {
385 MOZ_ASSERT(IsSetAndValid());
386 MOZ_ASSERT(!IsAtLastContent() && !IsEndOfContainer());
387 return ContainerAsText()->TextFragment().CharAt(mOffset
.value() + 1);
389 MOZ_NEVER_INLINE_DEBUG
bool IsNextCharASCIISpace() const {
390 return nsCRT::IsAsciiSpace(NextChar());
392 MOZ_NEVER_INLINE_DEBUG
bool IsNextCharNBSP() const {
393 return NextChar() == 0x00A0;
395 MOZ_NEVER_INLINE_DEBUG
bool IsNextCharASCIISpaceOrNBSP() const {
396 char16_t ch
= NextChar();
397 return nsCRT::IsAsciiSpace(ch
) || ch
== 0x00A0;
400 uint32_t Offset() const {
401 if (mOffset
.isSome()) {
402 MOZ_ASSERT(mOffset
.isSome());
403 return mOffset
.value();
409 MOZ_ASSERT(mParent
->IsContainerNode(),
410 "If the container cannot have children, mOffset.isSome() should "
413 // We're referring after the last child. Fix offset now.
414 const_cast<SelfType
*>(this)->mOffset
= mozilla::Some(mParent
->Length());
415 return mOffset
.value();
417 MOZ_ASSERT(mChild
->GetParentNode() == mParent
);
419 if (mChild
== mParent
->GetFirstChild()) {
420 const_cast<SelfType
*>(this)->mOffset
= mozilla::Some(0);
422 const_cast<SelfType
*>(this)->mOffset
=
423 mozilla::Some(mParent
->ComputeIndexOf(mChild
));
425 return mOffset
.value();
429 * Set() sets a point to aOffset or aChild.
430 * If it's set with aOffset, mChild is invalidated. If it's set with aChild,
431 * mOffset may be invalidated.
433 template <typename ContainerType
>
434 void Set(ContainerType
* aContainer
, int32_t aOffset
) {
435 mParent
= aContainer
;
437 mOffset
= mozilla::Some(aOffset
);
438 mIsChildInitialized
= false;
439 NS_ASSERTION(!mParent
|| mOffset
.value() <= mParent
->Length(),
440 "The offset is out of bounds");
442 template <typename ContainerType
, template <typename
> typename StrongPtr
>
443 void Set(const StrongPtr
<ContainerType
>& aContainer
, int32_t aOffset
) {
444 Set(aContainer
.get(), aOffset
);
446 void Set(const nsINode
* aChild
) {
448 if (NS_WARN_IF(!aChild
->IsContent())) {
452 mParent
= aChild
->GetParentNode();
453 mChild
= const_cast<nsIContent
*>(aChild
->AsContent());
455 mIsChildInitialized
= true;
459 * SetToEndOf() sets this to the end of aContainer. Then, mChild is always
460 * nullptr but marked as initialized and mOffset is always set.
462 template <typename ContainerType
>
463 MOZ_NEVER_INLINE_DEBUG
void SetToEndOf(const ContainerType
* aContainer
) {
464 MOZ_ASSERT(aContainer
);
465 mParent
= const_cast<ContainerType
*>(aContainer
);
467 mOffset
= mozilla::Some(mParent
->Length());
468 mIsChildInitialized
= true;
470 template <typename ContainerType
, template <typename
> typename StrongPtr
>
471 MOZ_NEVER_INLINE_DEBUG
void SetToEndOf(
472 const StrongPtr
<ContainerType
>& aContainer
) {
473 SetToEndOf(aContainer
.get());
475 template <typename ContainerType
>
476 MOZ_NEVER_INLINE_DEBUG
static SelfType
AtEndOf(
477 const ContainerType
& aContainer
) {
479 point
.SetToEndOf(&aContainer
);
482 template <typename ContainerType
, template <typename
> typename StrongPtr
>
483 MOZ_NEVER_INLINE_DEBUG
static SelfType
AtEndOf(
484 const StrongPtr
<ContainerType
>& aContainer
) {
485 MOZ_ASSERT(aContainer
.get());
486 return AtEndOf(*aContainer
.get());
490 * SetAfter() sets mChild to next sibling of aChild.
492 void SetAfter(const nsINode
* aChild
) {
494 nsIContent
* nextSibling
= aChild
->GetNextSibling();
499 nsINode
* parentNode
= aChild
->GetParentNode();
500 if (NS_WARN_IF(!parentNode
)) {
504 SetToEndOf(parentNode
);
506 template <typename ContainerType
>
507 static SelfType
After(const ContainerType
& aContainer
) {
509 point
.SetAfter(&aContainer
);
512 template <typename ContainerType
, template <typename
> typename StrongPtr
>
513 MOZ_NEVER_INLINE_DEBUG
static SelfType
After(
514 const StrongPtr
<ContainerType
>& aContainer
) {
515 MOZ_ASSERT(aContainer
.get());
516 return After(*aContainer
.get());
518 template <typename PT
, typename CT
>
519 MOZ_NEVER_INLINE_DEBUG
static SelfType
After(
520 const EditorDOMPointBase
<PT
, CT
>& aPoint
) {
521 MOZ_ASSERT(aPoint
.IsSet());
523 return After(*aPoint
.mChild
);
525 if (NS_WARN_IF(aPoint
.IsEndOfContainer())) {
528 SelfType
point(aPoint
);
529 MOZ_ALWAYS_TRUE(point
.AdvanceOffset());
534 * NextPoint() and PreviousPoint() returns next/previous DOM point in
537 MOZ_NEVER_INLINE_DEBUG SelfType
NextPoint() const {
538 NS_ASSERTION(!IsEndOfContainer(), "Should not be at end of the container");
539 SelfType
result(*this);
540 result
.AdvanceOffset();
543 MOZ_NEVER_INLINE_DEBUG SelfType
PreviousPoint() const {
544 NS_ASSERTION(!IsStartOfContainer(),
545 "Should not be at start of the container");
546 SelfType
result(*this);
547 result
.RewindOffset();
552 * Clear() makes the instance not point anywhere.
558 mIsChildInitialized
= false;
562 * AdvanceOffset() tries to refer next sibling of mChild and/of next offset.
563 * If the container can have children and there is no next sibling or the
564 * offset reached the length of the container, this outputs warning and does
565 * nothing. So, callers need to check if there is next sibling which you
568 * @return true if there is a next DOM point to refer.
570 bool AdvanceOffset() {
571 if (NS_WARN_IF(!mParent
)) {
574 // If only mOffset is available, just compute the offset.
575 if ((mOffset
.isSome() && !mIsChildInitialized
) ||
576 !mParent
->IsContainerNode()) {
577 MOZ_ASSERT(mOffset
.isSome());
579 if (NS_WARN_IF(mOffset
.value() >= mParent
->Length())) {
580 // We're already referring the start of the container.
583 mOffset
= mozilla::Some(mOffset
.value() + 1);
587 MOZ_ASSERT(mIsChildInitialized
);
588 MOZ_ASSERT(!mOffset
.isSome() || mOffset
.isSome());
589 if (NS_WARN_IF(!mParent
->HasChildren()) || NS_WARN_IF(!mChild
) ||
590 NS_WARN_IF(mOffset
.isSome() && mOffset
.value() >= mParent
->Length())) {
591 // We're already referring the end of the container (or outside).
595 if (mOffset
.isSome()) {
596 MOZ_ASSERT(mOffset
.isSome());
597 mOffset
= mozilla::Some(mOffset
.value() + 1);
599 mChild
= mChild
->GetNextSibling();
604 * RewindOffset() tries to refer previous sibling of mChild and/or previous
605 * offset. If the container can have children and there is no next previous
606 * or the offset is 0, this outputs warning and does nothing. So, callers
607 * need to check if there is previous sibling which you need to refer.
609 * @return true if there is a previous DOM point to refer.
611 bool RewindOffset() {
612 if (NS_WARN_IF(!mParent
)) {
615 // If only mOffset is available, just compute the offset.
616 if ((mOffset
.isSome() && !mIsChildInitialized
) ||
617 !mParent
->IsContainerNode()) {
618 MOZ_ASSERT(mOffset
.isSome());
620 if (NS_WARN_IF(!mOffset
.value()) ||
621 NS_WARN_IF(mOffset
.value() > mParent
->Length())) {
622 // We're already referring the start of the container or
623 // the offset is invalid since perhaps, the offset was set before
624 // the last DOM tree change.
627 mOffset
= mozilla::Some(mOffset
.value() - 1);
631 MOZ_ASSERT(mIsChildInitialized
);
632 MOZ_ASSERT(!mOffset
.isSome() || mOffset
.isSome());
633 if (NS_WARN_IF(!mParent
->HasChildren()) ||
634 NS_WARN_IF(mChild
&& !mChild
->GetPreviousSibling()) ||
635 NS_WARN_IF(mOffset
.isSome() && !mOffset
.value())) {
636 // We're already referring the start of the container (or the child has
637 // been moved from the container?).
641 nsIContent
* previousSibling
=
642 mChild
? mChild
->GetPreviousSibling() : mParent
->GetLastChild();
643 if (NS_WARN_IF(!previousSibling
)) {
644 // We're already referring the first child of the container.
648 if (mOffset
.isSome()) {
649 mOffset
= mozilla::Some(mOffset
.value() - 1);
651 mChild
= previousSibling
;
656 * GetNonAnonymousSubtreePoint() returns a DOM point which is NOT in
657 * native-anonymous subtree. If the instance isn't in native-anonymous
658 * subtree, this returns same point. Otherwise, climbs up until finding
659 * non-native-anonymous parent and returns the point of it. I.e.,
660 * container is parent of the found non-anonymous-native node.
662 EditorRawDOMPoint
GetNonAnonymousSubtreePoint() const {
663 if (NS_WARN_IF(!IsSet())) {
664 return EditorRawDOMPoint();
666 if (!IsInNativeAnonymousSubtree()) {
667 return EditorRawDOMPoint(*this);
670 for (parent
= mParent
->GetParentNode();
671 parent
&& parent
->IsInNativeAnonymousSubtree();
672 parent
= parent
->GetParentNode()) {
675 return EditorRawDOMPoint();
677 return EditorRawDOMPoint(parent
);
681 return mParent
&& (mIsChildInitialized
|| mOffset
.isSome());
684 bool IsSetAndValid() const {
689 if (mChild
&& mChild
->GetParentNode() != mParent
) {
692 if (mOffset
.isSome() && mOffset
.value() > mParent
->Length()) {
698 bool HasChildMovedFromContainer() const {
699 return mChild
&& mChild
->GetParentNode() != mParent
;
702 bool IsStartOfContainer() const {
703 // If we're referring the first point in the container:
704 // If mParent is not a container like a text node, mOffset is 0.
705 // If mChild is initialized and it's first child of mParent.
706 // If mChild isn't initialized and the offset is 0.
707 if (NS_WARN_IF(!mParent
)) {
710 if (!mParent
->IsContainerNode()) {
711 return !mOffset
.value();
713 if (mIsChildInitialized
) {
714 if (mParent
->GetFirstChild() == mChild
) {
715 NS_WARNING_ASSERTION(!mOffset
.isSome() || !mOffset
.value(),
716 "If mOffset was initialized, it should be 0");
719 NS_WARNING_ASSERTION(!mOffset
.isSome() || mParent
->GetChildAt_Deprecated(
720 mOffset
.value()) == mChild
,
721 "mOffset and mChild are mismatched");
724 MOZ_ASSERT(mOffset
.isSome());
725 return !mOffset
.value();
728 bool IsEndOfContainer() const {
729 // If we're referring after the last point of the container:
730 // If mParent is not a container like text node, mOffset is same as the
731 // length of the container.
732 // If mChild is initialized and it's nullptr.
733 // If mChild isn't initialized and mOffset is same as the length of the
735 if (NS_WARN_IF(!mParent
)) {
738 if (!mParent
->IsContainerNode()) {
739 return mOffset
.value() == mParent
->Length();
741 if (mIsChildInitialized
) {
743 NS_WARNING_ASSERTION(
744 !mOffset
.isSome() || mOffset
.value() == mParent
->Length(),
745 "If mOffset was initialized, it should be length of the container");
748 NS_WARNING_ASSERTION(!mOffset
.isSome() || mParent
->GetChildAt_Deprecated(
749 mOffset
.value()) == mChild
,
750 "mOffset and mChild are mismatched");
753 MOZ_ASSERT(mOffset
.isSome());
754 return mOffset
.value() == mParent
->Length();
758 * IsAtLastContent() returns true when it refers last child of the container
759 * or last character offset of text node.
761 bool IsAtLastContent() const {
762 if (NS_WARN_IF(!mParent
)) {
765 if (mParent
->IsContainerNode() && mOffset
.isSome()) {
766 return mOffset
.value() == mParent
->Length() - 1;
768 if (mIsChildInitialized
) {
769 if (mChild
&& mChild
== mParent
->GetLastChild()) {
770 NS_WARNING_ASSERTION(
771 !mOffset
.isSome() || mOffset
.value() == mParent
->Length() - 1,
772 "If mOffset was initialized, it should be length - 1 of the "
776 NS_WARNING_ASSERTION(!mOffset
.isSome() || mParent
->GetChildAt_Deprecated(
777 mOffset
.value()) == mChild
,
778 "mOffset and mChild are mismatched");
781 MOZ_ASSERT(mOffset
.isSome());
782 return mOffset
.value() == mParent
->Length() - 1;
785 bool IsBRElementAtEndOfContainer() const {
786 if (NS_WARN_IF(!mParent
)) {
789 if (!mParent
->IsContainerNode()) {
792 const_cast<SelfType
*>(this)->EnsureChild();
793 if (!mChild
|| mChild
->GetNextSibling()) {
796 return mChild
->IsHTMLElement(nsGkAtoms::br
);
799 template <typename A
, typename B
>
800 EditorDOMPointBase
& operator=(const RangeBoundaryBase
<A
, B
>& aOther
) {
801 mParent
= aOther
.mParent
;
802 mChild
= aOther
.mRef
? aOther
.mRef
->GetNextSibling()
803 : (aOther
.mParent
&& aOther
.mParent
->IsContainerNode()
804 ? aOther
.mParent
->GetFirstChild()
806 mOffset
= aOther
.mOffset
;
807 mIsChildInitialized
=
808 aOther
.mRef
|| (aOther
.mParent
&& !aOther
.mParent
->IsContainerNode()) ||
809 (aOther
.mOffset
.isSome() && !aOther
.mOffset
.value());
813 template <typename A
, typename B
>
814 EditorDOMPointBase
& operator=(const EditorDOMPointBase
<A
, B
>& aOther
) {
815 mParent
= aOther
.mParent
;
816 mChild
= aOther
.mChild
;
817 mOffset
= aOther
.mOffset
;
818 mIsChildInitialized
= aOther
.mIsChildInitialized
;
822 template <typename A
, typename B
>
823 bool operator==(const EditorDOMPointBase
<A
, B
>& aOther
) const {
824 if (mParent
!= aOther
.mParent
) {
828 if (mOffset
.isSome() && aOther
.mOffset
.isSome()) {
829 // If both mOffset are set, we need to compare both mRef too because
830 // the relation of mRef and mOffset have already broken by DOM tree
832 if (mOffset
!= aOther
.mOffset
) {
835 if (mChild
== aOther
.mChild
) {
838 if (NS_WARN_IF(mIsChildInitialized
&& aOther
.mIsChildInitialized
)) {
839 // In this case, relation between mChild and mOffset of one of or both
840 // of them doesn't match with current DOM tree since the DOM tree might
841 // have been changed after computing mChild or mOffset.
844 // If one of mChild hasn't been computed yet, we should compare them only
845 // with mOffset. Perhaps, we shouldn't copy mChild from non-nullptr one
846 // to the other since if we copy it here, it may be unexpected behavior
851 MOZ_ASSERT(mIsChildInitialized
|| aOther
.mIsChildInitialized
);
853 if (mOffset
.isSome() && !mIsChildInitialized
&& !aOther
.mOffset
.isSome() &&
854 aOther
.mIsChildInitialized
) {
855 // If this has only mOffset and the other has only mChild, this needs to
856 // compute mChild now.
857 const_cast<SelfType
*>(this)->EnsureChild();
858 return mChild
== aOther
.mChild
;
861 if (!mOffset
.isSome() && mIsChildInitialized
&& aOther
.mOffset
.isSome() &&
862 !aOther
.mIsChildInitialized
) {
863 // If this has only mChild and the other has only mOffset, the other needs
864 // to compute mChild now.
865 const_cast<EditorDOMPointBase
<A
, B
>&>(aOther
).EnsureChild();
866 return mChild
== aOther
.mChild
;
869 // If mOffset of one of them hasn't been computed from mChild yet, we should
870 // compare only with mChild. Perhaps, we shouldn't copy mOffset from being
871 // some one to not being some one since if we copy it here, it may be
872 // unexpected behavior for some callers.
873 return mChild
== aOther
.mChild
;
876 template <typename A
, typename B
>
877 bool operator==(const RangeBoundaryBase
<A
, B
>& aOther
) const {
878 // TODO: Optimize this with directly comparing with RangeBoundaryBase
880 return *this == SelfType(aOther
);
883 template <typename A
, typename B
>
884 bool operator!=(const EditorDOMPointBase
<A
, B
>& aOther
) const {
885 return !(*this == aOther
);
888 template <typename A
, typename B
>
889 bool operator!=(const RangeBoundaryBase
<A
, B
>& aOther
) const {
890 return !(*this == aOther
);
894 * This operator should be used if API of other modules take RawRangeBoundary,
895 * e.g., methods of Selection and nsRange.
897 operator const RawRangeBoundary() const { return ToRawRangeBoundary(); }
898 const RawRangeBoundary
ToRawRangeBoundary() const {
899 if (!IsSet() || NS_WARN_IF(!mIsChildInitialized
&& !mOffset
.isSome())) {
900 return RawRangeBoundary();
902 if (!mParent
->IsContainerNode()) {
903 MOZ_ASSERT(mOffset
.value() <= mParent
->Length());
904 // If the container is a data node like a text node, we need to create
905 // RangeBoundaryBase instance only with mOffset because mChild is always
907 return RawRangeBoundary(mParent
, mOffset
.value());
909 if (mIsChildInitialized
&& mOffset
.isSome()) {
910 // If we've already set both child and offset, we should create
911 // RangeBoundary with offset after validation.
914 MOZ_ASSERT(mParent
== mChild
->GetParentNode());
915 MOZ_ASSERT(mParent
->GetChildAt_Deprecated(mOffset
.value()) == mChild
);
917 MOZ_ASSERT(mParent
->Length() == mOffset
.value());
919 #endif // #ifdef DEBUG
920 return RawRangeBoundary(mParent
, mOffset
.value());
922 // Otherwise, we should create RangeBoundaryBase only with available
924 if (mOffset
.isSome()) {
925 return RawRangeBoundary(mParent
, mOffset
.value());
928 return RawRangeBoundary(mParent
, mChild
->GetPreviousSibling());
930 return RawRangeBoundary(mParent
, mParent
->GetLastChild());
933 EditorDOMPointInText
GetAsInText() const {
934 return IsInTextNode() ? EditorDOMPointInText(ContainerAsText(), Offset())
935 : EditorDOMPointInText();
937 MOZ_NEVER_INLINE_DEBUG EditorDOMPointInText
AsInText() const {
938 MOZ_ASSERT(IsInTextNode());
939 return EditorDOMPointInText(ContainerAsText(), Offset());
942 template <typename A
, typename B
>
943 bool IsBefore(const EditorDOMPointBase
<A
, B
>& aOther
) const {
944 if (!IsSetAndValid() || !aOther
.IsSetAndValid()) {
947 Maybe
<int32_t> comp
= nsContentUtils::ComparePoints(
948 ToRawRangeBoundary(), aOther
.ToRawRangeBoundary());
949 return comp
.isSome() && comp
.value() == -1;
952 template <typename A
, typename B
>
953 bool EqualsOrIsBefore(const EditorDOMPointBase
<A
, B
>& aOther
) const {
954 if (!IsSetAndValid() || !aOther
.IsSetAndValid()) {
957 Maybe
<int32_t> comp
= nsContentUtils::ComparePoints(
958 ToRawRangeBoundary(), aOther
.ToRawRangeBoundary());
959 return comp
.isSome() && comp
.value() <= 0;
964 if (mIsChildInitialized
) {
968 MOZ_ASSERT(!mOffset
.isSome());
971 MOZ_ASSERT(mOffset
.isSome());
972 MOZ_ASSERT(mOffset
.value() <= mParent
->Length());
973 mIsChildInitialized
= true;
974 if (!mParent
->IsContainerNode()) {
977 mChild
= mParent
->GetChildAt_Deprecated(mOffset
.value());
978 MOZ_ASSERT(mChild
|| mOffset
.value() == mParent
->Length());
984 mozilla::Maybe
<uint32_t> mOffset
;
986 bool mIsChildInitialized
;
988 template <typename PT
, typename CT
>
989 friend class EditorDOMPointBase
;
991 friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback
&,
992 EditorDOMPoint
&, const char*,
994 friend void ImplCycleCollectionUnlink(EditorDOMPoint
&);
997 inline void ImplCycleCollectionUnlink(EditorDOMPoint
& aField
) {
998 ImplCycleCollectionUnlink(aField
.mParent
);
999 ImplCycleCollectionUnlink(aField
.mChild
);
1002 inline void ImplCycleCollectionTraverse(
1003 nsCycleCollectionTraversalCallback
& aCallback
, EditorDOMPoint
& aField
,
1004 const char* aName
, uint32_t aFlags
) {
1005 ImplCycleCollectionTraverse(aCallback
, aField
.mParent
, "mParent", 0);
1006 ImplCycleCollectionTraverse(aCallback
, aField
.mChild
, "mChild", 0);
1009 template <typename EditorDOMPointType
>
1010 class EditorDOMRangeBase
;
1013 * EditorDOMRangeBase class stores a pair of same EditorDOMPointBase type.
1014 * The instance must be created with valid DOM points and start must be
1015 * before or same as end.
1018 typedef EditorDOMRangeBase
<EditorDOMPoint
> EditorDOMRange
;
1019 typedef EditorDOMRangeBase
<EditorRawDOMPoint
> EditorRawDOMRange
;
1020 typedef EditorDOMRangeBase
<EditorDOMPointInText
> EditorDOMRangeInTexts
;
1021 typedef EditorDOMRangeBase
<EditorRawDOMPointInText
> EditorRawDOMRangeInTexts
;
1023 template <typename EditorDOMPointType
>
1024 class EditorDOMRangeBase final
{
1026 EditorDOMRangeBase() = default;
1027 template <typename PT
, typename CT
>
1028 explicit EditorDOMRangeBase(const EditorDOMPointBase
<PT
, CT
>& aStart
)
1029 : mStart(aStart
), mEnd(aStart
) {
1030 MOZ_ASSERT(!mStart
.IsSet() || mStart
.IsSetAndValid());
1032 template <typename StartPointType
, typename EndPointType
>
1033 explicit EditorDOMRangeBase(const StartPointType
& aStart
,
1034 const EndPointType
& aEnd
)
1035 : mStart(aStart
), mEnd(aEnd
) {
1036 MOZ_ASSERT_IF(mStart
.IsSet(), mStart
.IsSetAndValid());
1037 MOZ_ASSERT_IF(mEnd
.IsSet(), mEnd
.IsSetAndValid());
1038 MOZ_ASSERT_IF(mStart
.IsSet() && mEnd
.IsSet(),
1039 mStart
.EqualsOrIsBefore(mEnd
));
1041 explicit EditorDOMRangeBase(const dom::AbstractRange
& aRange
)
1042 : mStart(aRange
.StartRef()), mEnd(aRange
.EndRef()) {
1043 MOZ_ASSERT_IF(mStart
.IsSet(), mStart
.IsSetAndValid());
1044 MOZ_ASSERT_IF(mEnd
.IsSet(), mEnd
.IsSetAndValid());
1045 MOZ_ASSERT_IF(mStart
.IsSet() && mEnd
.IsSet(),
1046 mStart
.EqualsOrIsBefore(mEnd
));
1049 template <typename PointType
>
1050 MOZ_NEVER_INLINE_DEBUG
void SetStart(const PointType
& aStart
) {
1053 template <typename PointType
>
1054 MOZ_NEVER_INLINE_DEBUG
void SetEnd(const PointType
& aEnd
) {
1057 template <typename StartPointType
, typename EndPointType
>
1058 MOZ_NEVER_INLINE_DEBUG
void SetStartAndEnd(const StartPointType
& aStart
,
1059 const EndPointType
& aEnd
) {
1060 MOZ_ASSERT_IF(aStart
.IsSet() && aEnd
.IsSet(),
1061 aStart
.EqualsOrIsBefore(aEnd
));
1066 const EditorDOMPointType
& StartRef() const { return mStart
; }
1067 const EditorDOMPointType
& EndRef() const { return mEnd
; }
1069 bool Collapsed() const {
1070 MOZ_ASSERT(IsPositioned());
1071 return mStart
== mEnd
;
1073 bool IsPositioned() const { return mStart
.IsSet() && mEnd
.IsSet(); }
1074 bool IsPositionedAndValid() const {
1075 return mStart
.IsSetAndValid() && mEnd
.IsSetAndValid() &&
1076 mStart
.EqualsOrIsBefore(mEnd
);
1078 template <typename OtherPointType
>
1079 MOZ_NEVER_INLINE_DEBUG
bool Contains(const OtherPointType
& aPoint
) const {
1080 MOZ_ASSERT(aPoint
.IsSetAndValid());
1081 return IsPositioned() && aPoint
.IsSet() &&
1082 mStart
.EqualsOrIsBefore(aPoint
) && aPoint
.IsBefore(mEnd
);
1084 bool InSameContainer() const {
1085 MOZ_ASSERT(IsPositioned());
1086 return IsPositioned() && mStart
.GetContainer() == mEnd
.GetContainer();
1088 bool IsInContentNodes() const {
1089 MOZ_ASSERT(IsPositioned());
1090 return IsPositioned() && mStart
.IsInContentNode() && mEnd
.IsInContentNode();
1092 bool IsInTextNodes() const {
1093 MOZ_ASSERT(IsPositioned());
1094 return IsPositioned() && mStart
.IsInTextNode() && mEnd
.IsInTextNode();
1096 template <typename OtherRangeType
>
1097 bool operator==(const OtherRangeType
& aOther
) const {
1098 return (!IsPositioned() && !aOther
.IsPositioned()) ||
1099 (mStart
== aOther
.mStart
&& mEnd
== aOther
.mEnd
);
1101 template <typename OtherRangeType
>
1102 bool operator!=(const OtherRangeType
& aOther
) const {
1103 return !(*this == aOther
);
1106 EditorDOMRangeInTexts
GetAsInTexts() const {
1107 return IsInTextNodes()
1108 ? EditorDOMRangeInTexts(mStart
.AsInText(), mEnd
.AsInText())
1109 : EditorDOMRangeInTexts();
1111 MOZ_NEVER_INLINE_DEBUG EditorDOMRangeInTexts
AsInTexts() const {
1112 MOZ_ASSERT(IsInTextNodes());
1113 return EditorDOMRangeInTexts(mStart
.AsInText(), mEnd
.AsInText());
1117 EditorDOMPointType mStart
;
1118 EditorDOMPointType mEnd
;
1120 friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback
&,
1121 EditorDOMRange
&, const char*,
1123 friend void ImplCycleCollectionUnlink(EditorDOMRange
&);
1126 inline void ImplCycleCollectionUnlink(EditorDOMRange
& aField
) {
1127 ImplCycleCollectionUnlink(aField
.mStart
);
1128 ImplCycleCollectionUnlink(aField
.mEnd
);
1131 inline void ImplCycleCollectionTraverse(
1132 nsCycleCollectionTraversalCallback
& aCallback
, EditorDOMRange
& aField
,
1133 const char* aName
, uint32_t aFlags
) {
1134 ImplCycleCollectionTraverse(aCallback
, aField
.mStart
, "mStart", 0);
1135 ImplCycleCollectionTraverse(aCallback
, aField
.mEnd
, "mEnd", 0);
1139 * AutoEditorDOMPointOffsetInvalidator is useful if DOM tree will be changed
1140 * when EditorDOMPoint instance is available and keeps referring same child
1143 * This class automatically guarantees that given EditorDOMPoint instance
1144 * stores the child node and invalidates its offset when the instance is
1145 * destroyed. Additionally, users of this class can invalidate the offset
1146 * manually when they need.
1148 class MOZ_STACK_CLASS AutoEditorDOMPointOffsetInvalidator final
{
1150 explicit AutoEditorDOMPointOffsetInvalidator(EditorDOMPoint
& aPoint
)
1151 : mPoint(aPoint
), mCanceled(false) {
1152 MOZ_ASSERT(aPoint
.IsSetAndValid());
1153 MOZ_ASSERT(mPoint
.CanContainerHaveChildren());
1154 mChild
= mPoint
.GetChild();
1157 ~AutoEditorDOMPointOffsetInvalidator() {
1164 * Manually, invalidate offset of the given point.
1166 void InvalidateOffset() {
1170 // If the point referred after the last child, let's keep referring
1171 // after current last node of the old container.
1172 mPoint
.SetToEndOf(mPoint
.GetContainer());
1177 * After calling Cancel(), mPoint won't be modified by the destructor.
1179 void Cancel() { mCanceled
= true; }
1182 EditorDOMPoint
& mPoint
;
1183 // Needs to store child node by ourselves because EditorDOMPoint stores
1184 // child node with mRef which is previous sibling of current child node.
1185 // Therefore, we cannot keep referring it if it's first child.
1186 nsCOMPtr
<nsIContent
> mChild
;
1190 AutoEditorDOMPointOffsetInvalidator() = delete;
1191 AutoEditorDOMPointOffsetInvalidator(
1192 const AutoEditorDOMPointOffsetInvalidator
& aOther
) = delete;
1193 const AutoEditorDOMPointOffsetInvalidator
& operator=(
1194 const AutoEditorDOMPointOffsetInvalidator
& aOther
) = delete;
1198 * AutoEditorDOMPointChildInvalidator is useful if DOM tree will be changed
1199 * when EditorDOMPoint instance is available and keeps referring same container
1202 * This class automatically guarantees that given EditorDOMPoint instance
1203 * stores offset and invalidates its child node when the instance is destroyed.
1204 * Additionally, users of this class can invalidate the child manually when
1207 class MOZ_STACK_CLASS AutoEditorDOMPointChildInvalidator final
{
1209 explicit AutoEditorDOMPointChildInvalidator(EditorDOMPoint
& aPoint
)
1210 : mPoint(aPoint
), mCanceled(false) {
1211 MOZ_ASSERT(aPoint
.IsSetAndValid());
1212 Unused
<< mPoint
.Offset();
1215 ~AutoEditorDOMPointChildInvalidator() {
1222 * Manually, invalidate child of the given point.
1224 void InvalidateChild() { mPoint
.Set(mPoint
.GetContainer(), mPoint
.Offset()); }
1227 * After calling Cancel(), mPoint won't be modified by the destructor.
1229 void Cancel() { mCanceled
= true; }
1232 EditorDOMPoint
& mPoint
;
1236 AutoEditorDOMPointChildInvalidator() = delete;
1237 AutoEditorDOMPointChildInvalidator(
1238 const AutoEditorDOMPointChildInvalidator
& aOther
) = delete;
1239 const AutoEditorDOMPointChildInvalidator
& operator=(
1240 const AutoEditorDOMPointChildInvalidator
& aOther
) = delete;
1243 } // namespace mozilla
1245 #endif // #ifndef mozilla_EditorDOMPoint_h