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/EditorForwards.h"
12 #include "mozilla/Maybe.h"
13 #include "mozilla/RangeBoundary.h"
14 #include "mozilla/ToString.h"
15 #include "mozilla/dom/AbstractRange.h"
16 #include "mozilla/dom/Element.h"
17 #include "mozilla/dom/Selection.h" // for Selection::InterlinePosition
18 #include "mozilla/dom/Text.h"
21 #include "nsContentUtils.h"
23 #include "nsGkAtoms.h"
24 #include "nsIContent.h"
26 #include "nsStyledElement.h"
28 #include <type_traits>
33 * EditorDOMPoint and EditorRawDOMPoint are simple classes which refers
34 * a point in the DOM tree at creating the instance or initializing the
35 * instance with calling Set().
37 * EditorDOMPoint refers container node (and child node if it's already set)
38 * with nsCOMPtr. EditorRawDOMPoint refers them with raw pointer.
39 * So, EditorRawDOMPoint is useful when you access the nodes only before
40 * changing DOM tree since increasing refcount may appear in micro benchmark
41 * if it's in a hot path. On the other hand, if you need to refer them even
42 * after changing DOM tree, you must use EditorDOMPoint.
44 * When initializing an instance only with child node or offset, the instance
45 * starts to refer the child node or offset in the container. In this case,
46 * the other information hasn't been initialized due to performance reason.
47 * When you retrieve the other information with calling Offset() or
48 * GetChild(), the other information is computed with the current DOM tree.
49 * Therefore, e.g., in the following case, the other information may be
52 * EditorDOMPoint pointA(container1, childNode1);
53 * EditorDOMPoint pointB(container1, childNode1);
54 * Unused << pointA.Offset(); // The offset is computed now.
55 * container1->RemoveChild(childNode1->GetPreviousSibling());
56 * Unused << pointB.Offset(); // Now, pointB.Offset() equals pointA.Offset() - 1
60 * EditorDOMPoint pointA(container1, 5);
61 * EditorDOMPoint pointB(container1, 5);
62 * Unused << pointA.GetChild(); // The child is computed now.
63 * container1->RemoveChild(childNode1->GetFirstChild());
64 * Unused << pointB.GetChild(); // Now, pointB.GetChild() equals
65 * // pointA.GetChild()->GetPreviousSibling().
67 * So, when you initialize an instance only with one information, you need to
68 * be careful when you access the other information after changing the DOM tree.
69 * When you need to lock the child node or offset and recompute the other
70 * information with new DOM tree, you can use
71 * AutoEditorDOMPointOffsetInvalidator and AutoEditorDOMPointChildInvalidator.
74 // FYI: Don't make the following instantiating macros end with `;` because
75 // using them without `;`, VSCode may be confused and cause wrong red-
76 // wavy underlines in the following code of the macro.
77 #define NS_INSTANTIATE_EDITOR_DOM_POINT_METHOD(aResultType, aMethodName, ...) \
78 template aResultType EditorDOMPoint::aMethodName(__VA_ARGS__); \
79 template aResultType EditorRawDOMPoint::aMethodName(__VA_ARGS__); \
80 template aResultType EditorDOMPointInText::aMethodName(__VA_ARGS__); \
81 template aResultType EditorRawDOMPointInText::aMethodName(__VA_ARGS__)
83 #define NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(aResultType, aMethodName, \
85 template aResultType EditorDOMPoint::aMethodName(__VA_ARGS__) const; \
86 template aResultType EditorRawDOMPoint::aMethodName(__VA_ARGS__) const; \
87 template aResultType EditorDOMPointInText::aMethodName(__VA_ARGS__) const; \
88 template aResultType EditorRawDOMPointInText::aMethodName(__VA_ARGS__) const
90 #define NS_INSTANTIATE_METHOD_RETURNING_ANY_EDITOR_DOM_POINT(aMethodName, ...) \
91 template EditorDOMPoint aMethodName(__VA_ARGS__); \
92 template EditorRawDOMPoint aMethodName(__VA_ARGS__); \
93 template EditorDOMPointInText aMethodName(__VA_ARGS__); \
94 template EditorRawDOMPointInText aMethodName(__VA_ARGS__)
96 #define NS_INSTANTIATE_CONST_METHOD_RETURNING_ANY_EDITOR_DOM_POINT( \
98 template EditorDOMPoint aMethodName(__VA_ARGS__) const; \
99 template EditorRawDOMPoint aMethodName(__VA_ARGS__) const; \
100 template EditorDOMPointInText aMethodName(__VA_ARGS__) const; \
101 template EditorRawDOMPointInText aMethodName(__VA_ARGS__) const
103 template <typename ParentType
, typename ChildType
>
104 class EditorDOMPointBase final
{
105 typedef EditorDOMPointBase
<ParentType
, ChildType
> SelfType
;
108 using InterlinePosition
= dom::Selection::InterlinePosition
;
110 EditorDOMPointBase() = default;
112 template <typename ContainerType
>
114 const ContainerType
* aContainer
, uint32_t aOffset
,
115 InterlinePosition aInterlinePosition
= InterlinePosition::Undefined
)
116 : mParent(const_cast<ContainerType
*>(aContainer
)),
118 mOffset(Some(aOffset
)),
119 mInterlinePosition(aInterlinePosition
) {
120 NS_WARNING_ASSERTION(
121 !mParent
|| mOffset
.value() <= mParent
->Length(),
122 "The offset is larger than the length of aContainer or negative");
128 template <typename ContainerType
, template <typename
> typename StrongPtr
>
130 const StrongPtr
<ContainerType
>& aContainer
, uint32_t aOffset
,
131 InterlinePosition aInterlinePosition
= InterlinePosition::Undefined
)
132 : EditorDOMPointBase(aContainer
.get(), aOffset
, aInterlinePosition
) {}
134 template <typename ContainerType
, template <typename
> typename StrongPtr
>
136 const StrongPtr
<const ContainerType
>& aContainer
, uint32_t aOffset
,
137 InterlinePosition aInterlinePosition
= InterlinePosition::Undefined
)
138 : EditorDOMPointBase(aContainer
.get(), aOffset
, aInterlinePosition
) {}
141 * Different from RangeBoundary, aPointedNode should be a child node
142 * which you want to refer.
144 explicit EditorDOMPointBase(
145 const nsINode
* aPointedNode
,
146 InterlinePosition aInterlinePosition
= InterlinePosition::Undefined
)
147 : mParent(aPointedNode
&& aPointedNode
->IsContent()
148 ? aPointedNode
->GetParentNode()
150 mChild(aPointedNode
&& aPointedNode
->IsContent()
151 ? const_cast<nsIContent
*>(aPointedNode
->AsContent())
153 mInterlinePosition(aInterlinePosition
) {
154 mIsChildInitialized
= aPointedNode
&& mChild
;
155 NS_WARNING_ASSERTION(IsSet(),
156 "The child is nullptr or doesn't have its parent");
157 NS_WARNING_ASSERTION(mChild
&& mChild
->GetParentNode() == mParent
,
158 "Initializing RangeBoundary with invalid value");
162 nsINode
* aContainer
, nsIContent
* aPointedNode
, uint32_t aOffset
,
163 InterlinePosition aInterlinePosition
= InterlinePosition::Undefined
)
164 : mParent(aContainer
),
165 mChild(aPointedNode
),
166 mOffset(mozilla::Some(aOffset
)),
167 mInterlinePosition(aInterlinePosition
),
168 mIsChildInitialized(true) {
169 MOZ_DIAGNOSTIC_ASSERT(
170 aContainer
, "This constructor shouldn't be used when pointing nowhere");
171 MOZ_ASSERT(mOffset
.value() <= mParent
->Length());
172 MOZ_ASSERT(mChild
|| mParent
->Length() == mOffset
.value() ||
173 !mParent
->IsContainerNode());
174 MOZ_ASSERT(!mChild
|| mParent
== mChild
->GetParentNode());
175 MOZ_ASSERT(mParent
->GetChildAt_Deprecated(mOffset
.value()) == mChild
);
178 template <typename PT
, typename CT
>
179 explicit EditorDOMPointBase(const RangeBoundaryBase
<PT
, CT
>& aOther
)
180 : mParent(aOther
.mParent
),
181 mChild(aOther
.mRef
? aOther
.mRef
->GetNextSibling()
182 : (aOther
.mParent
? aOther
.mParent
->GetFirstChild()
184 mOffset(aOther
.mOffset
),
185 mIsChildInitialized(aOther
.mRef
|| (aOther
.mOffset
.isSome() &&
186 !aOther
.mOffset
.value())) {}
188 void SetInterlinePosition(InterlinePosition aInterlinePosition
) {
190 mInterlinePosition
= aInterlinePosition
;
192 InterlinePosition
GetInterlinePosition() const {
193 return IsSet() ? mInterlinePosition
: InterlinePosition::Undefined
;
197 * GetContainer() returns the container node at the point.
198 * GetContainerAs*() returns the container node as specific type.
200 nsINode
* GetContainer() const { return mParent
; }
202 nsIContent
* GetContainerAsContent() const {
203 return nsIContent::FromNodeOrNull(mParent
);
206 MOZ_NEVER_INLINE_DEBUG nsIContent
* ContainerAsContent() const {
208 MOZ_ASSERT(mParent
->IsContent());
209 return mParent
->AsContent();
212 dom::Element
* GetContainerAsElement() const {
213 return dom::Element::FromNodeOrNull(mParent
);
216 MOZ_NEVER_INLINE_DEBUG
dom::Element
* ContainerAsElement() const {
218 MOZ_ASSERT(mParent
->IsElement());
219 return mParent
->AsElement();
222 nsStyledElement
* GetContainerAsStyledElement() const {
223 return nsStyledElement::FromNodeOrNull(mParent
);
226 dom::Text
* GetContainerAsText() const {
227 return dom::Text::FromNodeOrNull(mParent
);
230 MOZ_NEVER_INLINE_DEBUG
dom::Text
* ContainerAsText() const {
232 MOZ_ASSERT(IsInTextNode());
233 return mParent
->AsText();
237 * GetContainerParent() returns parent of the container node at the point.
239 nsINode
* GetContainerParent() const {
240 return mParent
? mParent
->GetParent() : nullptr;
243 nsIContent
* GetContainerParentAsContent() const {
244 return nsIContent::FromNodeOrNull(GetContainerParent());
247 dom::Element
* GetContainerParentAsElement() const {
248 return dom::Element::FromNodeOrNull(GetContainerParent());
251 dom::Element
* GetContainerOrContainerParentElement() const {
252 if (MOZ_UNLIKELY(!mParent
)) {
255 return mParent
->IsElement() ? ContainerAsElement()
256 : GetContainerParentAsElement();
260 * CanContainerHaveChildren() returns true if the container node can have
261 * child nodes. Otherwise, e.g., when the container is a text node, returns
264 bool CanContainerHaveChildren() const {
265 return mParent
&& mParent
->IsContainerNode();
269 * IsContainerEmpty() returns true if it has no children or its text is empty.
271 bool IsContainerEmpty() const { return mParent
&& !mParent
->Length(); }
274 * IsInContentNode() returns true if the container is a subclass of
277 bool IsInContentNode() const { return mParent
&& mParent
->IsContent(); }
280 * IsInDataNode() returns true if the container node is a data node including
283 bool IsInDataNode() const { return mParent
&& mParent
->IsCharacterData(); }
286 * IsInTextNode() returns true if the container node is a text node.
288 bool IsInTextNode() const { return mParent
&& mParent
->IsText(); }
291 * IsInNativeAnonymousSubtree() returns true if the container is in
292 * native anonymous subtree.
294 bool IsInNativeAnonymousSubtree() const {
295 return mParent
&& mParent
->IsInNativeAnonymousSubtree();
299 * IsContainerHTMLElement() returns true if the container node is an HTML
300 * element node and its node name is aTag.
302 bool IsContainerHTMLElement(nsAtom
* aTag
) const {
303 return mParent
&& mParent
->IsHTMLElement(aTag
);
307 * IsContainerAnyOfHTMLElements() returns true if the container node is an
308 * HTML element node and its node name is one of the arguments.
310 template <typename First
, typename
... Args
>
311 bool IsContainerAnyOfHTMLElements(First aFirst
, Args
... aArgs
) const {
312 return mParent
&& mParent
->IsAnyOfHTMLElements(aFirst
, aArgs
...);
316 * GetChild() returns a child node which is pointed by the instance.
317 * If mChild hasn't been initialized yet, this computes the child node
318 * from mParent and mOffset with *current* DOM tree.
320 nsIContent
* GetChild() const {
321 if (!mParent
|| !mParent
->IsContainerNode()) {
324 if (mIsChildInitialized
) {
327 // Fix child node now.
328 const_cast<SelfType
*>(this)->EnsureChild();
333 * GetCurrentChildAtOffset() returns current child at mOffset.
334 * I.e., mOffset needs to be fixed before calling this.
336 nsIContent
* GetCurrentChildAtOffset() const {
337 MOZ_ASSERT(mOffset
.isSome());
338 if (mOffset
.isNothing()) {
341 return mParent
? mParent
->GetChildAt_Deprecated(*mOffset
) : nullptr;
345 * GetChildOrContainerIfDataNode() returns the child content node,
346 * or container content node if the container is a data node.
348 nsIContent
* GetChildOrContainerIfDataNode() const {
349 if (IsInDataNode()) {
350 return ContainerAsContent();
356 * GetNextSiblingOfChild() returns next sibling of the child node.
357 * If this refers after the last child or the container cannot have children,
358 * this returns nullptr with warning.
359 * If mChild hasn't been initialized yet, this computes the child node
360 * from mParent and mOffset with *current* DOM tree.
362 nsIContent
* GetNextSiblingOfChild() const {
363 if (NS_WARN_IF(!mParent
) || NS_WARN_IF(!mParent
->IsContainerNode())) {
366 if (mIsChildInitialized
) {
367 return mChild
? mChild
->GetNextSibling() : nullptr;
369 MOZ_ASSERT(mOffset
.isSome());
370 if (NS_WARN_IF(mOffset
.value() > mParent
->Length())) {
371 // If this has been set only offset and now the offset is invalid,
372 // let's just return nullptr.
375 // Fix child node now.
376 const_cast<SelfType
*>(this)->EnsureChild();
377 return mChild
? mChild
->GetNextSibling() : nullptr;
381 * GetPreviousSiblingOfChild() returns previous sibling of a child
382 * at offset. If this refers the first child or the container cannot have
383 * children, this returns nullptr with warning.
384 * If mChild hasn't been initialized yet, this computes the child node
385 * from mParent and mOffset with *current* DOM tree.
387 nsIContent
* GetPreviousSiblingOfChild() const {
388 if (NS_WARN_IF(!mParent
) || NS_WARN_IF(!mParent
->IsContainerNode())) {
391 if (mIsChildInitialized
) {
392 return mChild
? mChild
->GetPreviousSibling() : mParent
->GetLastChild();
394 MOZ_ASSERT(mOffset
.isSome());
395 if (NS_WARN_IF(mOffset
.value() > mParent
->Length())) {
396 // If this has been set only offset and now the offset is invalid,
397 // let's just return nullptr.
400 // Fix child node now.
401 const_cast<SelfType
*>(this)->EnsureChild();
402 return mChild
? mChild
->GetPreviousSibling() : mParent
->GetLastChild();
406 * Simple accessors of the character in dom::Text so that when you call
407 * these methods, you need to guarantee that the container is a dom::Text.
409 MOZ_NEVER_INLINE_DEBUG char16_t
Char() const {
410 MOZ_ASSERT(IsSetAndValid());
411 MOZ_ASSERT(!IsEndOfContainer());
412 return ContainerAsText()->TextFragment().CharAt(mOffset
.value());
414 MOZ_NEVER_INLINE_DEBUG
bool IsCharASCIISpace() const {
415 return nsCRT::IsAsciiSpace(Char());
417 MOZ_NEVER_INLINE_DEBUG
bool IsCharNBSP() const { return Char() == 0x00A0; }
418 MOZ_NEVER_INLINE_DEBUG
bool IsCharASCIISpaceOrNBSP() const {
419 char16_t ch
= Char();
420 return nsCRT::IsAsciiSpace(ch
) || ch
== 0x00A0;
422 MOZ_NEVER_INLINE_DEBUG
bool IsCharNewLine() const { return Char() == '\n'; }
423 MOZ_NEVER_INLINE_DEBUG
bool IsCharPreformattedNewLine() const;
424 MOZ_NEVER_INLINE_DEBUG
bool
425 IsCharPreformattedNewLineCollapsedWithWhiteSpaces() const;
427 * IsCharCollapsibleASCIISpace(), IsCharCollapsibleNBSP() and
428 * IsCharCollapsibleASCIISpaceOrNBSP() checks whether the white-space is
429 * preformatted or collapsible with the style of the container text node
430 * without flushing pending notifications.
432 bool IsCharCollapsibleASCIISpace() const;
433 bool IsCharCollapsibleNBSP() const;
434 bool IsCharCollapsibleASCIISpaceOrNBSP() const;
436 MOZ_NEVER_INLINE_DEBUG
bool IsCharHighSurrogateFollowedByLowSurrogate()
438 MOZ_ASSERT(IsSetAndValid());
439 MOZ_ASSERT(!IsEndOfContainer());
440 return ContainerAsText()
442 .IsHighSurrogateFollowedByLowSurrogateAt(mOffset
.value());
444 MOZ_NEVER_INLINE_DEBUG
bool IsCharLowSurrogateFollowingHighSurrogate() const {
445 MOZ_ASSERT(IsSetAndValid());
446 MOZ_ASSERT(!IsEndOfContainer());
447 return ContainerAsText()
449 .IsLowSurrogateFollowingHighSurrogateAt(mOffset
.value());
452 MOZ_NEVER_INLINE_DEBUG char16_t
PreviousChar() const {
453 MOZ_ASSERT(IsSetAndValid());
454 MOZ_ASSERT(!IsStartOfContainer());
455 return ContainerAsText()->TextFragment().CharAt(mOffset
.value() - 1);
457 MOZ_NEVER_INLINE_DEBUG
bool IsPreviousCharASCIISpace() const {
458 return nsCRT::IsAsciiSpace(PreviousChar());
460 MOZ_NEVER_INLINE_DEBUG
bool IsPreviousCharNBSP() const {
461 return PreviousChar() == 0x00A0;
463 MOZ_NEVER_INLINE_DEBUG
bool IsPreviousCharASCIISpaceOrNBSP() const {
464 char16_t ch
= PreviousChar();
465 return nsCRT::IsAsciiSpace(ch
) || ch
== 0x00A0;
467 MOZ_NEVER_INLINE_DEBUG
bool IsPreviousCharNewLine() const {
468 return PreviousChar() == '\n';
470 MOZ_NEVER_INLINE_DEBUG
bool IsPreviousCharPreformattedNewLine() const;
471 MOZ_NEVER_INLINE_DEBUG
bool
472 IsPreviousCharPreformattedNewLineCollapsedWithWhiteSpaces() const;
474 * IsPreviousCharCollapsibleASCIISpace(), IsPreviousCharCollapsibleNBSP() and
475 * IsPreviousCharCollapsibleASCIISpaceOrNBSP() checks whether the white-space
476 * is preformatted or collapsible with the style of the container text node
477 * without flushing pending notifications.
479 bool IsPreviousCharCollapsibleASCIISpace() const;
480 bool IsPreviousCharCollapsibleNBSP() const;
481 bool IsPreviousCharCollapsibleASCIISpaceOrNBSP() const;
483 MOZ_NEVER_INLINE_DEBUG char16_t
NextChar() const {
484 MOZ_ASSERT(IsSetAndValid());
485 MOZ_ASSERT(!IsAtLastContent() && !IsEndOfContainer());
486 return ContainerAsText()->TextFragment().CharAt(mOffset
.value() + 1);
488 MOZ_NEVER_INLINE_DEBUG
bool IsNextCharASCIISpace() const {
489 return nsCRT::IsAsciiSpace(NextChar());
491 MOZ_NEVER_INLINE_DEBUG
bool IsNextCharNBSP() const {
492 return NextChar() == 0x00A0;
494 MOZ_NEVER_INLINE_DEBUG
bool IsNextCharASCIISpaceOrNBSP() const {
495 char16_t ch
= NextChar();
496 return nsCRT::IsAsciiSpace(ch
) || ch
== 0x00A0;
498 MOZ_NEVER_INLINE_DEBUG
bool IsNextCharNewLine() const {
499 return NextChar() == '\n';
501 MOZ_NEVER_INLINE_DEBUG
bool IsNextCharPreformattedNewLine() const;
502 MOZ_NEVER_INLINE_DEBUG
bool
503 IsNextCharPreformattedNewLineCollapsedWithWhiteSpaces() const;
505 * IsNextCharCollapsibleASCIISpace(), IsNextCharCollapsibleNBSP() and
506 * IsNextCharCollapsibleASCIISpaceOrNBSP() checks whether the white-space is
507 * preformatted or collapsible with the style of the container text node
508 * without flushing pending notifications.
510 bool IsNextCharCollapsibleASCIISpace() const;
511 bool IsNextCharCollapsibleNBSP() const;
512 bool IsNextCharCollapsibleASCIISpaceOrNBSP() const;
514 uint32_t Offset() const {
515 if (mOffset
.isSome()) {
516 MOZ_ASSERT(mOffset
.isSome());
517 return mOffset
.value();
519 if (MOZ_UNLIKELY(!mParent
)) {
523 MOZ_ASSERT(mParent
->IsContainerNode(),
524 "If the container cannot have children, mOffset.isSome() should "
527 // We're referring after the last child. Fix offset now.
528 const_cast<SelfType
*>(this)->mOffset
= mozilla::Some(mParent
->Length());
529 return mOffset
.value();
531 MOZ_ASSERT(mChild
->GetParentNode() == mParent
);
533 if (mChild
== mParent
->GetFirstChild()) {
534 const_cast<SelfType
*>(this)->mOffset
= mozilla::Some(0u);
537 const_cast<SelfType
*>(this)->mOffset
= mParent
->ComputeIndexOf(mChild
);
538 MOZ_DIAGNOSTIC_ASSERT(mOffset
.isSome());
539 return mOffset
.valueOr(0u); // Avoid crash in Release/Beta
543 * Set() sets a point to aOffset or aChild.
544 * If it's set with aOffset, mChild is invalidated. If it's set with aChild,
545 * mOffset may be invalidated.
547 template <typename ContainerType
>
548 void Set(ContainerType
* aContainer
, uint32_t aOffset
) {
549 mParent
= aContainer
;
551 mOffset
= mozilla::Some(aOffset
);
552 mIsChildInitialized
= false;
553 mInterlinePosition
= InterlinePosition::Undefined
;
554 NS_ASSERTION(!mParent
|| mOffset
.value() <= mParent
->Length(),
555 "The offset is out of bounds");
557 template <typename ContainerType
, template <typename
> typename StrongPtr
>
558 void Set(const StrongPtr
<ContainerType
>& aContainer
, uint32_t aOffset
) {
559 Set(aContainer
.get(), aOffset
);
561 void Set(const nsINode
* aChild
) {
563 if (NS_WARN_IF(!aChild
->IsContent())) {
567 mParent
= aChild
->GetParentNode();
568 mChild
= const_cast<nsIContent
*>(aChild
->AsContent());
570 mIsChildInitialized
= true;
571 mInterlinePosition
= InterlinePosition::Undefined
;
575 * SetToEndOf() sets this to the end of aContainer. Then, mChild is always
576 * nullptr but marked as initialized and mOffset is always set.
578 template <typename ContainerType
>
579 MOZ_NEVER_INLINE_DEBUG
void SetToEndOf(const ContainerType
* aContainer
) {
580 MOZ_ASSERT(aContainer
);
581 mParent
= const_cast<ContainerType
*>(aContainer
);
583 mOffset
= mozilla::Some(mParent
->Length());
584 mIsChildInitialized
= true;
585 mInterlinePosition
= InterlinePosition::Undefined
;
587 template <typename ContainerType
, template <typename
> typename StrongPtr
>
588 MOZ_NEVER_INLINE_DEBUG
void SetToEndOf(
589 const StrongPtr
<ContainerType
>& aContainer
) {
590 SetToEndOf(aContainer
.get());
592 template <typename ContainerType
>
593 MOZ_NEVER_INLINE_DEBUG
static SelfType
AtEndOf(
594 const ContainerType
& aContainer
,
595 InterlinePosition aInterlinePosition
= InterlinePosition::Undefined
) {
597 point
.SetToEndOf(&aContainer
);
598 point
.mInterlinePosition
= aInterlinePosition
;
601 template <typename ContainerType
, template <typename
> typename StrongPtr
>
602 MOZ_NEVER_INLINE_DEBUG
static SelfType
AtEndOf(
603 const StrongPtr
<ContainerType
>& aContainer
,
604 InterlinePosition aInterlinePosition
= InterlinePosition::Undefined
) {
605 MOZ_ASSERT(aContainer
.get());
606 return AtEndOf(*aContainer
.get(), aInterlinePosition
);
610 * SetAfter() sets mChild to next sibling of aChild.
612 void SetAfter(const nsINode
* aChild
) {
614 nsIContent
* nextSibling
= aChild
->GetNextSibling();
619 nsINode
* parentNode
= aChild
->GetParentNode();
620 if (NS_WARN_IF(!parentNode
)) {
624 SetToEndOf(parentNode
);
626 template <typename ContainerType
>
627 static SelfType
After(
628 const ContainerType
& aContainer
,
629 InterlinePosition aInterlinePosition
= InterlinePosition::Undefined
) {
631 point
.SetAfter(&aContainer
);
632 point
.mInterlinePosition
= aInterlinePosition
;
635 template <typename ContainerType
, template <typename
> typename StrongPtr
>
636 MOZ_NEVER_INLINE_DEBUG
static SelfType
After(
637 const StrongPtr
<ContainerType
>& aContainer
,
638 InterlinePosition aInterlinePosition
= InterlinePosition::Undefined
) {
639 MOZ_ASSERT(aContainer
.get());
640 return After(*aContainer
.get(), aInterlinePosition
);
642 template <typename PT
, typename CT
>
643 MOZ_NEVER_INLINE_DEBUG
static SelfType
After(
644 const EditorDOMPointBase
<PT
, CT
>& aPoint
,
645 InterlinePosition aInterlinePosition
= InterlinePosition::Undefined
) {
646 MOZ_ASSERT(aPoint
.IsSet());
648 return After(*aPoint
.mChild
, aInterlinePosition
);
650 if (NS_WARN_IF(aPoint
.IsEndOfContainer())) {
653 auto point
= aPoint
.NextPoint().template To
<SelfType
>();
654 point
.mInterlinePosition
= aInterlinePosition
;
659 * ParentPoint() returns a point whose child is the container.
661 template <typename EditorDOMPointType
= SelfType
>
662 EditorDOMPointType
ParentPoint() const {
664 if (MOZ_UNLIKELY(!mParent
) || !mParent
->IsContent()) {
665 return EditorDOMPointType();
667 return EditorDOMPointType(ContainerAsContent());
671 * NextPoint() and PreviousPoint() returns next/previous DOM point in
674 template <typename EditorDOMPointType
= SelfType
>
675 EditorDOMPointType
NextPoint() const {
676 NS_ASSERTION(!IsEndOfContainer(), "Should not be at end of the container");
677 auto result
= this->template To
<EditorDOMPointType
>();
678 result
.AdvanceOffset();
681 template <typename EditorDOMPointType
= SelfType
>
682 EditorDOMPointType
PreviousPoint() const {
683 NS_ASSERTION(!IsStartOfContainer(),
684 "Should not be at start of the container");
685 EditorDOMPointType result
= this->template To
<EditorDOMPointType
>();
686 result
.RewindOffset();
691 * Clear() makes the instance not point anywhere.
697 mIsChildInitialized
= false;
698 mInterlinePosition
= InterlinePosition::Undefined
;
702 * AdvanceOffset() tries to refer next sibling of mChild and/of next offset.
703 * If the container can have children and there is no next sibling or the
704 * offset reached the length of the container, this outputs warning and does
705 * nothing. So, callers need to check if there is next sibling which you
708 * @return true if there is a next DOM point to refer.
710 bool AdvanceOffset() {
711 if (NS_WARN_IF(!mParent
)) {
714 // If only mOffset is available, just compute the offset.
715 if ((mOffset
.isSome() && !mIsChildInitialized
) ||
716 !mParent
->IsContainerNode()) {
717 MOZ_ASSERT(mOffset
.isSome());
719 if (NS_WARN_IF(mOffset
.value() >= mParent
->Length())) {
720 // We're already referring the start of the container.
723 mOffset
= mozilla::Some(mOffset
.value() + 1);
724 mInterlinePosition
= InterlinePosition::Undefined
;
728 MOZ_ASSERT(mIsChildInitialized
);
729 MOZ_ASSERT(!mOffset
.isSome() || mOffset
.isSome());
730 if (NS_WARN_IF(!mParent
->HasChildren()) || NS_WARN_IF(!mChild
) ||
731 NS_WARN_IF(mOffset
.isSome() && mOffset
.value() >= mParent
->Length())) {
732 // We're already referring the end of the container (or outside).
736 if (mOffset
.isSome()) {
737 MOZ_ASSERT(mOffset
.isSome());
738 mOffset
= mozilla::Some(mOffset
.value() + 1);
740 mChild
= mChild
->GetNextSibling();
741 mInterlinePosition
= InterlinePosition::Undefined
;
746 * RewindOffset() tries to refer previous sibling of mChild and/or previous
747 * offset. If the container can have children and there is no next previous
748 * or the offset is 0, this outputs warning and does nothing. So, callers
749 * need to check if there is previous sibling which you need to refer.
751 * @return true if there is a previous DOM point to refer.
753 bool RewindOffset() {
754 if (NS_WARN_IF(!mParent
)) {
757 // If only mOffset is available, just compute the offset.
758 if ((mOffset
.isSome() && !mIsChildInitialized
) ||
759 !mParent
->IsContainerNode()) {
760 MOZ_ASSERT(mOffset
.isSome());
762 if (NS_WARN_IF(!mOffset
.value()) ||
763 NS_WARN_IF(mOffset
.value() > mParent
->Length())) {
764 // We're already referring the start of the container or
765 // the offset is invalid since perhaps, the offset was set before
766 // the last DOM tree change.
767 NS_ASSERTION(false, "Failed to rewind offset");
770 mOffset
= mozilla::Some(mOffset
.value() - 1);
771 mInterlinePosition
= InterlinePosition::Undefined
;
775 MOZ_ASSERT(mIsChildInitialized
);
776 MOZ_ASSERT(!mOffset
.isSome() || mOffset
.isSome());
777 if (NS_WARN_IF(!mParent
->HasChildren()) ||
778 NS_WARN_IF(mChild
&& !mChild
->GetPreviousSibling()) ||
779 NS_WARN_IF(mOffset
.isSome() && !mOffset
.value())) {
780 // We're already referring the start of the container (or the child has
781 // been moved from the container?).
785 nsIContent
* previousSibling
=
786 mChild
? mChild
->GetPreviousSibling() : mParent
->GetLastChild();
787 if (NS_WARN_IF(!previousSibling
)) {
788 // We're already referring the first child of the container.
792 if (mOffset
.isSome()) {
793 mOffset
= mozilla::Some(mOffset
.value() - 1);
795 mChild
= previousSibling
;
796 mInterlinePosition
= InterlinePosition::Undefined
;
801 * GetNonAnonymousSubtreePoint() returns a DOM point which is NOT in
802 * native-anonymous subtree. If the instance isn't in native-anonymous
803 * subtree, this returns same point. Otherwise, climbs up until finding
804 * non-native-anonymous parent and returns the point of it. I.e.,
805 * container is parent of the found non-anonymous-native node.
807 template <typename EditorDOMPointType
>
808 EditorDOMPointType
GetNonAnonymousSubtreePoint() const {
809 if (NS_WARN_IF(!IsSet())) {
810 return EditorDOMPointType();
812 if (!IsInNativeAnonymousSubtree()) {
813 return this->template To
<EditorDOMPointType
>();
816 for (parent
= mParent
->GetParentNode();
817 parent
&& parent
->IsInNativeAnonymousSubtree();
818 parent
= parent
->GetParentNode()) {
821 return EditorDOMPointType();
823 return EditorDOMPointType(parent
);
827 return mParent
&& (mIsChildInitialized
|| mOffset
.isSome());
830 bool IsSetAndValid() const {
836 (mChild
->GetParentNode() != mParent
|| mChild
->IsBeingRemoved())) {
839 if (mOffset
.isSome() && mOffset
.value() > mParent
->Length()) {
845 bool HasChildMovedFromContainer() const {
846 return mChild
&& mChild
->GetParentNode() != mParent
;
849 bool IsStartOfContainer() const {
850 // If we're referring the first point in the container:
851 // If mParent is not a container like a text node, mOffset is 0.
852 // If mChild is initialized and it's first child of mParent.
853 // If mChild isn't initialized and the offset is 0.
854 if (NS_WARN_IF(!mParent
)) {
857 if (!mParent
->IsContainerNode()) {
858 return !mOffset
.value();
860 if (mIsChildInitialized
) {
861 if (mParent
->GetFirstChild() == mChild
) {
862 NS_WARNING_ASSERTION(!mOffset
.isSome() || !mOffset
.value(),
863 "If mOffset was initialized, it should be 0");
866 NS_WARNING_ASSERTION(!mOffset
.isSome() || mParent
->GetChildAt_Deprecated(
867 mOffset
.value()) == mChild
,
868 "mOffset and mChild are mismatched");
871 MOZ_ASSERT(mOffset
.isSome());
872 return !mOffset
.value();
875 bool IsEndOfContainer() const {
876 // If we're referring after the last point of the container:
877 // If mParent is not a container like text node, mOffset is same as the
878 // length of the container.
879 // If mChild is initialized and it's nullptr.
880 // If mChild isn't initialized and mOffset is same as the length of the
882 if (NS_WARN_IF(!mParent
)) {
885 if (!mParent
->IsContainerNode()) {
886 return mOffset
.value() == mParent
->Length();
888 if (mIsChildInitialized
) {
890 NS_WARNING_ASSERTION(
891 !mOffset
.isSome() || mOffset
.value() == mParent
->Length(),
892 "If mOffset was initialized, it should be length of the container");
895 NS_WARNING_ASSERTION(!mOffset
.isSome() || mParent
->GetChildAt_Deprecated(
896 mOffset
.value()) == mChild
,
897 "mOffset and mChild are mismatched");
900 MOZ_ASSERT(mOffset
.isSome());
901 return mOffset
.value() == mParent
->Length();
905 * IsAtLastContent() returns true when it refers last child of the container
906 * or last character offset of text node.
908 bool IsAtLastContent() const {
909 if (NS_WARN_IF(!mParent
)) {
912 if (mParent
->IsContainerNode() && mOffset
.isSome()) {
913 return mOffset
.value() == mParent
->Length() - 1;
915 if (mIsChildInitialized
) {
916 if (mChild
&& mChild
== mParent
->GetLastChild()) {
917 NS_WARNING_ASSERTION(
918 !mOffset
.isSome() || mOffset
.value() == mParent
->Length() - 1,
919 "If mOffset was initialized, it should be length - 1 of the "
923 NS_WARNING_ASSERTION(!mOffset
.isSome() || mParent
->GetChildAt_Deprecated(
924 mOffset
.value()) == mChild
,
925 "mOffset and mChild are mismatched");
928 MOZ_ASSERT(mOffset
.isSome());
929 return mOffset
.value() == mParent
->Length() - 1;
932 bool IsBRElementAtEndOfContainer() const {
933 if (NS_WARN_IF(!mParent
)) {
936 if (!mParent
->IsContainerNode()) {
939 const_cast<SelfType
*>(this)->EnsureChild();
940 if (!mChild
|| mChild
->GetNextSibling()) {
943 return mChild
->IsHTMLElement(nsGkAtoms::br
);
946 template <typename A
, typename B
>
947 EditorDOMPointBase
& operator=(const RangeBoundaryBase
<A
, B
>& aOther
) {
948 mParent
= aOther
.mParent
;
949 mChild
= aOther
.mRef
? aOther
.mRef
->GetNextSibling()
950 : (aOther
.mParent
&& aOther
.mParent
->IsContainerNode()
951 ? aOther
.mParent
->GetFirstChild()
953 mOffset
= aOther
.mOffset
;
954 mIsChildInitialized
=
955 aOther
.mRef
|| (aOther
.mParent
&& !aOther
.mParent
->IsContainerNode()) ||
956 (aOther
.mOffset
.isSome() && !aOther
.mOffset
.value());
957 mInterlinePosition
= InterlinePosition::Undefined
;
961 template <typename EditorDOMPointType
>
962 constexpr EditorDOMPointType
To() const {
963 // XXX Cannot specialize this method due to implicit instantiatation caused
964 // by the inline CC functions below.
965 if (std::is_same
<SelfType
, EditorDOMPointType
>::value
) {
966 return reinterpret_cast<const EditorDOMPointType
&>(*this);
968 EditorDOMPointType result
;
969 result
.mParent
= mParent
;
970 result
.mChild
= mChild
;
971 result
.mOffset
= mOffset
;
972 result
.mIsChildInitialized
= mIsChildInitialized
;
973 result
.mInterlinePosition
= mInterlinePosition
;
978 * Don't compare mInterlinePosition. If it's required to check, perhaps,
979 * another compare operator like `===` should be created.
981 template <typename A
, typename B
>
982 bool operator==(const EditorDOMPointBase
<A
, B
>& aOther
) const {
983 if (mParent
!= aOther
.mParent
) {
987 if (mOffset
.isSome() && aOther
.mOffset
.isSome()) {
988 // If both mOffset are set, we need to compare both mRef too because
989 // the relation of mRef and mOffset have already broken by DOM tree
991 if (mOffset
!= aOther
.mOffset
) {
994 if (mChild
== aOther
.mChild
) {
997 if (NS_WARN_IF(mIsChildInitialized
&& aOther
.mIsChildInitialized
)) {
998 // In this case, relation between mChild and mOffset of one of or both
999 // of them doesn't match with current DOM tree since the DOM tree might
1000 // have been changed after computing mChild or mOffset.
1003 // If one of mChild hasn't been computed yet, we should compare them only
1004 // with mOffset. Perhaps, we shouldn't copy mChild from non-nullptr one
1005 // to the other since if we copy it here, it may be unexpected behavior
1006 // for some callers.
1010 MOZ_ASSERT(mIsChildInitialized
|| aOther
.mIsChildInitialized
);
1012 if (mOffset
.isSome() && !mIsChildInitialized
&& !aOther
.mOffset
.isSome() &&
1013 aOther
.mIsChildInitialized
) {
1014 // If this has only mOffset and the other has only mChild, this needs to
1015 // compute mChild now.
1016 const_cast<SelfType
*>(this)->EnsureChild();
1017 return mChild
== aOther
.mChild
;
1020 if (!mOffset
.isSome() && mIsChildInitialized
&& aOther
.mOffset
.isSome() &&
1021 !aOther
.mIsChildInitialized
) {
1022 // If this has only mChild and the other has only mOffset, the other needs
1023 // to compute mChild now.
1024 const_cast<EditorDOMPointBase
<A
, B
>&>(aOther
).EnsureChild();
1025 return mChild
== aOther
.mChild
;
1028 // If mOffset of one of them hasn't been computed from mChild yet, we should
1029 // compare only with mChild. Perhaps, we shouldn't copy mOffset from being
1030 // some one to not being some one since if we copy it here, it may be
1031 // unexpected behavior for some callers.
1032 return mChild
== aOther
.mChild
;
1035 template <typename A
, typename B
>
1036 bool operator==(const RangeBoundaryBase
<A
, B
>& aOther
) const {
1037 // TODO: Optimize this with directly comparing with RangeBoundaryBase
1039 return *this == SelfType(aOther
);
1042 template <typename A
, typename B
>
1043 bool operator!=(const EditorDOMPointBase
<A
, B
>& aOther
) const {
1044 return !(*this == aOther
);
1047 template <typename A
, typename B
>
1048 bool operator!=(const RangeBoundaryBase
<A
, B
>& aOther
) const {
1049 return !(*this == aOther
);
1053 * This operator should be used if API of other modules take RawRangeBoundary,
1054 * e.g., methods of Selection and nsRange.
1056 operator const RawRangeBoundary() const { return ToRawRangeBoundary(); }
1057 const RawRangeBoundary
ToRawRangeBoundary() const {
1058 if (!IsSet() || NS_WARN_IF(!mIsChildInitialized
&& !mOffset
.isSome())) {
1059 return RawRangeBoundary();
1061 if (!mParent
->IsContainerNode()) {
1062 MOZ_ASSERT(mOffset
.value() <= mParent
->Length());
1063 // If the container is a data node like a text node, we need to create
1064 // RangeBoundaryBase instance only with mOffset because mChild is always
1066 return RawRangeBoundary(mParent
, mOffset
.value());
1068 if (mIsChildInitialized
&& mOffset
.isSome()) {
1069 // If we've already set both child and offset, we should create
1070 // RangeBoundary with offset after validation.
1073 MOZ_ASSERT(mParent
== mChild
->GetParentNode());
1074 MOZ_ASSERT(mParent
->GetChildAt_Deprecated(mOffset
.value()) == mChild
);
1076 MOZ_ASSERT(mParent
->Length() == mOffset
.value());
1078 #endif // #ifdef DEBUG
1079 return RawRangeBoundary(mParent
, mOffset
.value());
1081 // Otherwise, we should create RangeBoundaryBase only with available
1083 if (mOffset
.isSome()) {
1084 return RawRangeBoundary(mParent
, mOffset
.value());
1087 return RawRangeBoundary(mParent
, mChild
->GetPreviousSibling());
1089 return RawRangeBoundary(mParent
, mParent
->GetLastChild());
1092 EditorDOMPointInText
GetAsInText() const {
1093 return IsInTextNode() ? EditorDOMPointInText(ContainerAsText(), Offset(),
1095 : EditorDOMPointInText();
1097 MOZ_NEVER_INLINE_DEBUG EditorDOMPointInText
AsInText() const {
1098 MOZ_ASSERT(IsInTextNode());
1099 return EditorDOMPointInText(ContainerAsText(), Offset(),
1100 mInterlinePosition
);
1103 template <typename A
, typename B
>
1104 bool IsBefore(const EditorDOMPointBase
<A
, B
>& aOther
) const {
1105 if (!IsSetAndValid() || !aOther
.IsSetAndValid()) {
1108 Maybe
<int32_t> comp
= nsContentUtils::ComparePoints(
1109 ToRawRangeBoundary(), aOther
.ToRawRangeBoundary());
1110 return comp
.isSome() && comp
.value() == -1;
1113 template <typename A
, typename B
>
1114 bool EqualsOrIsBefore(const EditorDOMPointBase
<A
, B
>& aOther
) const {
1115 if (!IsSetAndValid() || !aOther
.IsSetAndValid()) {
1118 Maybe
<int32_t> comp
= nsContentUtils::ComparePoints(
1119 ToRawRangeBoundary(), aOther
.ToRawRangeBoundary());
1120 return comp
.isSome() && comp
.value() <= 0;
1123 friend std::ostream
& operator<<(std::ostream
& aStream
,
1124 const SelfType
& aDOMPoint
) {
1125 aStream
<< "{ mParent=" << aDOMPoint
.mParent
.get();
1126 if (aDOMPoint
.mParent
) {
1127 aStream
<< " (" << *aDOMPoint
.mParent
1128 << ", Length()=" << aDOMPoint
.mParent
->Length() << ")";
1130 aStream
<< ", mChild=" << aDOMPoint
.mChild
.get();
1131 if (aDOMPoint
.mChild
) {
1132 aStream
<< " (" << *aDOMPoint
.mChild
<< ")";
1134 aStream
<< ", mOffset=" << aDOMPoint
.mOffset
<< ", mIsChildInitialized="
1135 << (aDOMPoint
.mIsChildInitialized
? "true" : "false")
1136 << ", mInterlinePosition=" << aDOMPoint
.mInterlinePosition
<< " }";
1141 void EnsureChild() {
1142 if (mIsChildInitialized
) {
1146 MOZ_ASSERT(!mOffset
.isSome());
1149 MOZ_ASSERT(mOffset
.isSome());
1150 MOZ_ASSERT(mOffset
.value() <= mParent
->Length());
1151 mIsChildInitialized
= true;
1152 if (!mParent
->IsContainerNode()) {
1155 mChild
= mParent
->GetChildAt_Deprecated(mOffset
.value());
1156 MOZ_ASSERT(mChild
|| mOffset
.value() == mParent
->Length());
1159 ParentType mParent
= nullptr;
1160 ChildType mChild
= nullptr;
1162 Maybe
<uint32_t> mOffset
;
1163 InterlinePosition mInterlinePosition
= InterlinePosition::Undefined
;
1164 bool mIsChildInitialized
= false;
1166 template <typename PT
, typename CT
>
1167 friend class EditorDOMPointBase
;
1169 friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback
&,
1170 EditorDOMPoint
&, const char*,
1172 friend void ImplCycleCollectionUnlink(EditorDOMPoint
&);
1175 inline void ImplCycleCollectionUnlink(EditorDOMPoint
& aField
) {
1176 ImplCycleCollectionUnlink(aField
.mParent
);
1177 ImplCycleCollectionUnlink(aField
.mChild
);
1180 inline void ImplCycleCollectionTraverse(
1181 nsCycleCollectionTraversalCallback
& aCallback
, EditorDOMPoint
& aField
,
1182 const char* aName
, uint32_t aFlags
) {
1183 ImplCycleCollectionTraverse(aCallback
, aField
.mParent
, "mParent", 0);
1184 ImplCycleCollectionTraverse(aCallback
, aField
.mChild
, "mChild", 0);
1188 * EditorDOMRangeBase class stores a pair of same EditorDOMPointBase type.
1189 * The instance must be created with valid DOM points and start must be
1190 * before or same as end.
1192 template <typename EditorDOMPointType
>
1193 class EditorDOMRangeBase final
{
1195 using PointType
= EditorDOMPointType
;
1197 EditorDOMRangeBase() = default;
1198 template <typename PT
, typename CT
>
1199 explicit EditorDOMRangeBase(const EditorDOMPointBase
<PT
, CT
>& aStart
)
1200 : mStart(aStart
), mEnd(aStart
) {
1201 MOZ_ASSERT(!mStart
.IsSet() || mStart
.IsSetAndValid());
1203 template <typename StartPointType
, typename EndPointType
>
1204 explicit EditorDOMRangeBase(const StartPointType
& aStart
,
1205 const EndPointType
& aEnd
)
1206 : mStart(aStart
.template To
<PointType
>()),
1207 mEnd(aEnd
.template To
<PointType
>()) {
1208 MOZ_ASSERT_IF(mStart
.IsSet(), mStart
.IsSetAndValid());
1209 MOZ_ASSERT_IF(mEnd
.IsSet(), mEnd
.IsSetAndValid());
1210 MOZ_ASSERT_IF(mStart
.IsSet() && mEnd
.IsSet(),
1211 mStart
.EqualsOrIsBefore(mEnd
));
1213 explicit EditorDOMRangeBase(const dom::AbstractRange
& aRange
)
1214 : mStart(aRange
.StartRef()), mEnd(aRange
.EndRef()) {
1215 MOZ_ASSERT_IF(mStart
.IsSet(), mStart
.IsSetAndValid());
1216 MOZ_ASSERT_IF(mEnd
.IsSet(), mEnd
.IsSetAndValid());
1217 MOZ_ASSERT_IF(mStart
.IsSet() && mEnd
.IsSet(),
1218 mStart
.EqualsOrIsBefore(mEnd
));
1221 template <typename MaybeOtherPointType
>
1222 MOZ_NEVER_INLINE_DEBUG
void SetStart(const MaybeOtherPointType
& aStart
) {
1223 mStart
= aStart
.template To
<PointType
>();
1225 template <typename MaybeOtherPointType
>
1226 MOZ_NEVER_INLINE_DEBUG
void SetEnd(const MaybeOtherPointType
& aEnd
) {
1227 mEnd
= aEnd
.template To
<PointType
>();
1229 template <typename StartPointType
, typename EndPointType
>
1230 MOZ_NEVER_INLINE_DEBUG
void SetStartAndEnd(const StartPointType
& aStart
,
1231 const EndPointType
& aEnd
) {
1232 MOZ_ASSERT_IF(aStart
.IsSet() && aEnd
.IsSet(),
1233 aStart
.EqualsOrIsBefore(aEnd
));
1234 mStart
= aStart
.template To
<PointType
>();
1235 mEnd
= aEnd
.template To
<PointType
>();
1242 const PointType
& StartRef() const { return mStart
; }
1243 const PointType
& EndRef() const { return mEnd
; }
1245 bool Collapsed() const {
1246 MOZ_ASSERT(IsPositioned());
1247 return mStart
== mEnd
;
1249 bool IsPositioned() const { return mStart
.IsSet() && mEnd
.IsSet(); }
1250 bool IsPositionedAndValid() const {
1251 return mStart
.IsSetAndValid() && mEnd
.IsSetAndValid() &&
1252 mStart
.EqualsOrIsBefore(mEnd
);
1254 template <typename OtherPointType
>
1255 MOZ_NEVER_INLINE_DEBUG
bool Contains(const OtherPointType
& aPoint
) const {
1256 MOZ_ASSERT(aPoint
.IsSetAndValid());
1257 return IsPositioned() && aPoint
.IsSet() &&
1258 mStart
.EqualsOrIsBefore(aPoint
) && aPoint
.IsBefore(mEnd
);
1260 bool InSameContainer() const {
1261 MOZ_ASSERT(IsPositioned());
1262 return IsPositioned() && mStart
.GetContainer() == mEnd
.GetContainer();
1264 bool IsInContentNodes() const {
1265 MOZ_ASSERT(IsPositioned());
1266 return IsPositioned() && mStart
.IsInContentNode() && mEnd
.IsInContentNode();
1268 bool IsInTextNodes() const {
1269 MOZ_ASSERT(IsPositioned());
1270 return IsPositioned() && mStart
.IsInTextNode() && mEnd
.IsInTextNode();
1272 template <typename OtherRangeType
>
1273 bool operator==(const OtherRangeType
& aOther
) const {
1274 return (!IsPositioned() && !aOther
.IsPositioned()) ||
1275 (mStart
== aOther
.mStart
&& mEnd
== aOther
.mEnd
);
1277 template <typename OtherRangeType
>
1278 bool operator!=(const OtherRangeType
& aOther
) const {
1279 return !(*this == aOther
);
1282 EditorDOMRangeInTexts
GetAsInTexts() const {
1283 return IsInTextNodes()
1284 ? EditorDOMRangeInTexts(mStart
.AsInText(), mEnd
.AsInText())
1285 : EditorDOMRangeInTexts();
1287 MOZ_NEVER_INLINE_DEBUG EditorDOMRangeInTexts
AsInTexts() const {
1288 MOZ_ASSERT(IsInTextNodes());
1289 return EditorDOMRangeInTexts(mStart
.AsInText(), mEnd
.AsInText());
1292 bool EnsureNotInNativeAnonymousSubtree() {
1293 if (mStart
.IsInNativeAnonymousSubtree()) {
1294 nsIContent
* parent
= nullptr;
1295 for (parent
= mStart
.ContainerAsContent()
1296 ->GetClosestNativeAnonymousSubtreeRootParent();
1297 parent
&& parent
->IsInNativeAnonymousSubtree();
1298 parent
= parent
->GetClosestNativeAnonymousSubtreeRootParent()) {
1300 if (MOZ_UNLIKELY(!parent
)) {
1305 if (mEnd
.IsInNativeAnonymousSubtree()) {
1306 nsIContent
* parent
= nullptr;
1307 for (parent
= mEnd
.ContainerAsContent()
1308 ->GetClosestNativeAnonymousSubtreeRootParent();
1309 parent
&& parent
->IsInNativeAnonymousSubtree();
1310 parent
= parent
->GetClosestNativeAnonymousSubtreeRootParent()) {
1312 if (MOZ_UNLIKELY(!parent
)) {
1315 mEnd
.SetAfter(parent
);
1321 EditorDOMPointType mStart
;
1322 EditorDOMPointType mEnd
;
1324 friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback
&,
1325 EditorDOMRange
&, const char*,
1327 friend void ImplCycleCollectionUnlink(EditorDOMRange
&);
1330 inline void ImplCycleCollectionUnlink(EditorDOMRange
& aField
) {
1331 ImplCycleCollectionUnlink(aField
.mStart
);
1332 ImplCycleCollectionUnlink(aField
.mEnd
);
1335 inline void ImplCycleCollectionTraverse(
1336 nsCycleCollectionTraversalCallback
& aCallback
, EditorDOMRange
& aField
,
1337 const char* aName
, uint32_t aFlags
) {
1338 ImplCycleCollectionTraverse(aCallback
, aField
.mStart
, "mStart", 0);
1339 ImplCycleCollectionTraverse(aCallback
, aField
.mEnd
, "mEnd", 0);
1343 * AutoEditorDOMPointOffsetInvalidator is useful if DOM tree will be changed
1344 * when EditorDOMPoint instance is available and keeps referring same child
1347 * This class automatically guarantees that given EditorDOMPoint instance
1348 * stores the child node and invalidates its offset when the instance is
1349 * destroyed. Additionally, users of this class can invalidate the offset
1350 * manually when they need.
1352 class MOZ_STACK_CLASS AutoEditorDOMPointOffsetInvalidator final
{
1354 explicit AutoEditorDOMPointOffsetInvalidator(EditorDOMPoint
& aPoint
)
1355 : mPoint(aPoint
), mCanceled(false) {
1356 MOZ_ASSERT(aPoint
.IsSetAndValid());
1357 MOZ_ASSERT(mPoint
.CanContainerHaveChildren());
1358 mChild
= mPoint
.GetChild();
1361 ~AutoEditorDOMPointOffsetInvalidator() {
1368 * Manually, invalidate offset of the given point.
1370 void InvalidateOffset() {
1374 // If the point referred after the last child, let's keep referring
1375 // after current last node of the old container.
1376 mPoint
.SetToEndOf(mPoint
.GetContainer());
1381 * After calling Cancel(), mPoint won't be modified by the destructor.
1383 void Cancel() { mCanceled
= true; }
1386 EditorDOMPoint
& mPoint
;
1387 // Needs to store child node by ourselves because EditorDOMPoint stores
1388 // child node with mRef which is previous sibling of current child node.
1389 // Therefore, we cannot keep referring it if it's first child.
1390 nsCOMPtr
<nsIContent
> mChild
;
1394 AutoEditorDOMPointOffsetInvalidator() = delete;
1395 AutoEditorDOMPointOffsetInvalidator(
1396 const AutoEditorDOMPointOffsetInvalidator
& aOther
) = delete;
1397 const AutoEditorDOMPointOffsetInvalidator
& operator=(
1398 const AutoEditorDOMPointOffsetInvalidator
& aOther
) = delete;
1402 * AutoEditorDOMPointChildInvalidator is useful if DOM tree will be changed
1403 * when EditorDOMPoint instance is available and keeps referring same container
1406 * This class automatically guarantees that given EditorDOMPoint instance
1407 * stores offset and invalidates its child node when the instance is destroyed.
1408 * Additionally, users of this class can invalidate the child manually when
1411 class MOZ_STACK_CLASS AutoEditorDOMPointChildInvalidator final
{
1413 explicit AutoEditorDOMPointChildInvalidator(EditorDOMPoint
& aPoint
)
1414 : mPoint(aPoint
), mCanceled(false) {
1415 MOZ_ASSERT(aPoint
.IsSetAndValid());
1416 Unused
<< mPoint
.Offset();
1419 ~AutoEditorDOMPointChildInvalidator() {
1426 * Manually, invalidate child of the given point.
1428 void InvalidateChild() { mPoint
.Set(mPoint
.GetContainer(), mPoint
.Offset()); }
1431 * After calling Cancel(), mPoint won't be modified by the destructor.
1433 void Cancel() { mCanceled
= true; }
1436 EditorDOMPoint
& mPoint
;
1440 AutoEditorDOMPointChildInvalidator() = delete;
1441 AutoEditorDOMPointChildInvalidator(
1442 const AutoEditorDOMPointChildInvalidator
& aOther
) = delete;
1443 const AutoEditorDOMPointChildInvalidator
& operator=(
1444 const AutoEditorDOMPointChildInvalidator
& aOther
) = delete;
1447 } // namespace mozilla
1449 #endif // #ifndef mozilla_EditorDOMPoint_h