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 using SelfType
= EditorDOMPointBase
<ParentType
, ChildType
>;
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
; }
201 template <typename ContentNodeType
>
202 ContentNodeType
* GetContainerAs() const {
203 return ContentNodeType::FromNodeOrNull(mParent
);
207 * ContainerAs() returns the container node with just casting to the specific
208 * type. Therefore, callers need to guarantee that the result is not nullptr
211 template <typename ContentNodeType
>
212 ContentNodeType
* ContainerAs() const {
214 MOZ_DIAGNOSTIC_ASSERT(ContentNodeType::FromNode(mParent
));
215 return static_cast<ContentNodeType
*>(GetContainer());
219 * GetContainerParent() returns parent of the container node at the point.
221 nsINode
* GetContainerParent() const {
222 return mParent
? mParent
->GetParent() : nullptr;
224 template <typename ContentNodeType
>
225 ContentNodeType
* GetContainerParentAs() const {
226 return ContentNodeType::FromNodeOrNull(GetContainerParent());
228 template <typename ContentNodeType
>
229 ContentNodeType
* ContainerParentAs() const {
230 MOZ_DIAGNOSTIC_ASSERT(GetContainerParentAs
<ContentNodeType
>());
231 return static_cast<ContentNodeType
*>(GetContainerParent());
234 dom::Element
* GetContainerOrContainerParentElement() const {
235 if (MOZ_UNLIKELY(!mParent
)) {
238 return mParent
->IsElement() ? ContainerAs
<dom::Element
>()
239 : GetContainerParentAs
<dom::Element
>();
243 * CanContainerHaveChildren() returns true if the container node can have
244 * child nodes. Otherwise, e.g., when the container is a text node, returns
247 bool CanContainerHaveChildren() const {
248 return mParent
&& mParent
->IsContainerNode();
252 * IsContainerEmpty() returns true if it has no children or its text is empty.
254 bool IsContainerEmpty() const { return mParent
&& !mParent
->Length(); }
257 * IsInContentNode() returns true if the container is a subclass of
260 bool IsInContentNode() const { return mParent
&& mParent
->IsContent(); }
263 * IsInDataNode() returns true if the container node is a data node including
266 bool IsInDataNode() const { return mParent
&& mParent
->IsCharacterData(); }
269 * IsInTextNode() returns true if the container node is a text node.
271 bool IsInTextNode() const { return mParent
&& mParent
->IsText(); }
274 * IsInNativeAnonymousSubtree() returns true if the container is in
275 * native anonymous subtree.
277 bool IsInNativeAnonymousSubtree() const {
278 return mParent
&& mParent
->IsInNativeAnonymousSubtree();
282 * IsContainerHTMLElement() returns true if the container node is an HTML
283 * element node and its node name is aTag.
285 bool IsContainerHTMLElement(nsAtom
* aTag
) const {
286 return mParent
&& mParent
->IsHTMLElement(aTag
);
290 * IsContainerAnyOfHTMLElements() returns true if the container node is an
291 * HTML element node and its node name is one of the arguments.
293 template <typename First
, typename
... Args
>
294 bool IsContainerAnyOfHTMLElements(First aFirst
, Args
... aArgs
) const {
295 return mParent
&& mParent
->IsAnyOfHTMLElements(aFirst
, aArgs
...);
299 * GetChild() returns a child node which is pointed by the instance.
300 * If mChild hasn't been initialized yet, this computes the child node
301 * from mParent and mOffset with *current* DOM tree.
303 nsIContent
* GetChild() const {
304 if (!mParent
|| !mParent
->IsContainerNode()) {
307 if (mIsChildInitialized
) {
310 // Fix child node now.
311 const_cast<SelfType
*>(this)->EnsureChild();
315 template <typename ContentNodeType
>
316 ContentNodeType
* GetChildAs() const {
317 return ContentNodeType::FromNodeOrNull(GetChild());
319 template <typename ContentNodeType
>
320 ContentNodeType
* ChildAs() const {
321 MOZ_DIAGNOSTIC_ASSERT(GetChildAs
<ContentNodeType
>());
322 return static_cast<ContentNodeType
*>(GetChild());
326 * GetCurrentChildAtOffset() returns current child at mOffset.
327 * I.e., mOffset needs to be fixed before calling this.
329 nsIContent
* GetCurrentChildAtOffset() const {
330 MOZ_ASSERT(mOffset
.isSome());
331 if (mOffset
.isNothing()) {
334 return mParent
? mParent
->GetChildAt_Deprecated(*mOffset
) : nullptr;
338 * GetChildOrContainerIfDataNode() returns the child content node,
339 * or container content node if the container is a data node.
341 nsIContent
* GetChildOrContainerIfDataNode() const {
342 if (IsInDataNode()) {
343 return ContainerAs
<nsIContent
>();
349 * GetNextSiblingOfChild() returns next sibling of the child node.
350 * If this refers after the last child or the container cannot have children,
351 * this returns nullptr with warning.
352 * If mChild hasn't been initialized yet, this computes the child node
353 * from mParent and mOffset with *current* DOM tree.
355 nsIContent
* GetNextSiblingOfChild() const {
356 if (NS_WARN_IF(!mParent
) || !mParent
->IsContainerNode()) {
359 if (mIsChildInitialized
) {
360 return mChild
? mChild
->GetNextSibling() : nullptr;
362 MOZ_ASSERT(mOffset
.isSome());
363 if (NS_WARN_IF(mOffset
.value() > mParent
->Length())) {
364 // If this has been set only offset and now the offset is invalid,
365 // let's just return nullptr.
368 // Fix child node now.
369 const_cast<SelfType
*>(this)->EnsureChild();
370 return mChild
? mChild
->GetNextSibling() : nullptr;
372 template <typename ContentNodeType
>
373 ContentNodeType
* GetNextSiblingOfChildAs() const {
374 return ContentNodeType::FromNodeOrNull(GetNextSiblingOfChild());
376 template <typename ContentNodeType
>
377 ContentNodeType
* NextSiblingOfChildAs() const {
379 MOZ_DIAGNOSTIC_ASSERT(GetNextSiblingOfChildAs
<ContentNodeType
>());
380 return static_cast<ContentNodeType
*>(GetNextSiblingOfChild());
384 * GetPreviousSiblingOfChild() returns previous sibling of a child
385 * at offset. If this refers the first child or the container cannot have
386 * children, this returns nullptr with warning.
387 * If mChild hasn't been initialized yet, this computes the child node
388 * from mParent and mOffset with *current* DOM tree.
390 nsIContent
* GetPreviousSiblingOfChild() const {
391 if (NS_WARN_IF(!mParent
) || !mParent
->IsContainerNode()) {
394 if (mIsChildInitialized
) {
395 return mChild
? mChild
->GetPreviousSibling() : mParent
->GetLastChild();
397 MOZ_ASSERT(mOffset
.isSome());
398 if (NS_WARN_IF(mOffset
.value() > mParent
->Length())) {
399 // If this has been set only offset and now the offset is invalid,
400 // let's just return nullptr.
403 // Fix child node now.
404 const_cast<SelfType
*>(this)->EnsureChild();
405 return mChild
? mChild
->GetPreviousSibling() : mParent
->GetLastChild();
407 template <typename ContentNodeType
>
408 ContentNodeType
* GetPreviousSiblingOfChildAs() const {
409 return ContentNodeType::FromNodeOrNull(GetPreviousSiblingOfChild());
411 template <typename ContentNodeType
>
412 ContentNodeType
* PreviousSiblingOfChildAs() const {
414 MOZ_DIAGNOSTIC_ASSERT(GetPreviousSiblingOfChildAs
<ContentNodeType
>());
415 return static_cast<ContentNodeType
*>(GetPreviousSiblingOfChild());
419 * Simple accessors of the character in dom::Text so that when you call
420 * these methods, you need to guarantee that the container is a dom::Text.
422 MOZ_NEVER_INLINE_DEBUG char16_t
Char() const {
423 MOZ_ASSERT(IsSetAndValid());
424 MOZ_ASSERT(!IsEndOfContainer());
425 return ContainerAs
<dom::Text
>()->TextFragment().CharAt(mOffset
.value());
427 MOZ_NEVER_INLINE_DEBUG
bool IsCharASCIISpace() const {
428 return nsCRT::IsAsciiSpace(Char());
430 MOZ_NEVER_INLINE_DEBUG
bool IsCharNBSP() const { return Char() == 0x00A0; }
431 MOZ_NEVER_INLINE_DEBUG
bool IsCharASCIISpaceOrNBSP() const {
432 char16_t ch
= Char();
433 return nsCRT::IsAsciiSpace(ch
) || ch
== 0x00A0;
435 MOZ_NEVER_INLINE_DEBUG
bool IsCharNewLine() const { return Char() == '\n'; }
436 MOZ_NEVER_INLINE_DEBUG
bool IsCharPreformattedNewLine() const;
437 MOZ_NEVER_INLINE_DEBUG
bool
438 IsCharPreformattedNewLineCollapsedWithWhiteSpaces() const;
440 * IsCharCollapsibleASCIISpace(), IsCharCollapsibleNBSP() and
441 * IsCharCollapsibleASCIISpaceOrNBSP() checks whether the white-space is
442 * preformatted or collapsible with the style of the container text node
443 * without flushing pending notifications.
445 bool IsCharCollapsibleASCIISpace() const;
446 bool IsCharCollapsibleNBSP() const;
447 bool IsCharCollapsibleASCIISpaceOrNBSP() const;
449 MOZ_NEVER_INLINE_DEBUG
bool IsCharHighSurrogateFollowedByLowSurrogate()
451 MOZ_ASSERT(IsSetAndValid());
452 MOZ_ASSERT(!IsEndOfContainer());
453 return ContainerAs
<dom::Text
>()
455 .IsHighSurrogateFollowedByLowSurrogateAt(mOffset
.value());
457 MOZ_NEVER_INLINE_DEBUG
bool IsCharLowSurrogateFollowingHighSurrogate() const {
458 MOZ_ASSERT(IsSetAndValid());
459 MOZ_ASSERT(!IsEndOfContainer());
460 return ContainerAs
<dom::Text
>()
462 .IsLowSurrogateFollowingHighSurrogateAt(mOffset
.value());
465 MOZ_NEVER_INLINE_DEBUG char16_t
PreviousChar() const {
466 MOZ_ASSERT(IsSetAndValid());
467 MOZ_ASSERT(!IsStartOfContainer());
468 return ContainerAs
<dom::Text
>()->TextFragment().CharAt(mOffset
.value() - 1);
470 MOZ_NEVER_INLINE_DEBUG
bool IsPreviousCharASCIISpace() const {
471 return nsCRT::IsAsciiSpace(PreviousChar());
473 MOZ_NEVER_INLINE_DEBUG
bool IsPreviousCharNBSP() const {
474 return PreviousChar() == 0x00A0;
476 MOZ_NEVER_INLINE_DEBUG
bool IsPreviousCharASCIISpaceOrNBSP() const {
477 char16_t ch
= PreviousChar();
478 return nsCRT::IsAsciiSpace(ch
) || ch
== 0x00A0;
480 MOZ_NEVER_INLINE_DEBUG
bool IsPreviousCharNewLine() const {
481 return PreviousChar() == '\n';
483 MOZ_NEVER_INLINE_DEBUG
bool IsPreviousCharPreformattedNewLine() const;
484 MOZ_NEVER_INLINE_DEBUG
bool
485 IsPreviousCharPreformattedNewLineCollapsedWithWhiteSpaces() const;
487 * IsPreviousCharCollapsibleASCIISpace(), IsPreviousCharCollapsibleNBSP() and
488 * IsPreviousCharCollapsibleASCIISpaceOrNBSP() checks whether the white-space
489 * is preformatted or collapsible with the style of the container text node
490 * without flushing pending notifications.
492 bool IsPreviousCharCollapsibleASCIISpace() const;
493 bool IsPreviousCharCollapsibleNBSP() const;
494 bool IsPreviousCharCollapsibleASCIISpaceOrNBSP() const;
496 MOZ_NEVER_INLINE_DEBUG char16_t
NextChar() const {
497 MOZ_ASSERT(IsSetAndValid());
498 MOZ_ASSERT(!IsAtLastContent() && !IsEndOfContainer());
499 return ContainerAs
<dom::Text
>()->TextFragment().CharAt(mOffset
.value() + 1);
501 MOZ_NEVER_INLINE_DEBUG
bool IsNextCharASCIISpace() const {
502 return nsCRT::IsAsciiSpace(NextChar());
504 MOZ_NEVER_INLINE_DEBUG
bool IsNextCharNBSP() const {
505 return NextChar() == 0x00A0;
507 MOZ_NEVER_INLINE_DEBUG
bool IsNextCharASCIISpaceOrNBSP() const {
508 char16_t ch
= NextChar();
509 return nsCRT::IsAsciiSpace(ch
) || ch
== 0x00A0;
511 MOZ_NEVER_INLINE_DEBUG
bool IsNextCharNewLine() const {
512 return NextChar() == '\n';
514 MOZ_NEVER_INLINE_DEBUG
bool IsNextCharPreformattedNewLine() const;
515 MOZ_NEVER_INLINE_DEBUG
bool
516 IsNextCharPreformattedNewLineCollapsedWithWhiteSpaces() const;
518 * IsNextCharCollapsibleASCIISpace(), IsNextCharCollapsibleNBSP() and
519 * IsNextCharCollapsibleASCIISpaceOrNBSP() checks whether the white-space is
520 * preformatted or collapsible with the style of the container text node
521 * without flushing pending notifications.
523 bool IsNextCharCollapsibleASCIISpace() const;
524 bool IsNextCharCollapsibleNBSP() const;
525 bool IsNextCharCollapsibleASCIISpaceOrNBSP() const;
527 [[nodiscard
]] bool HasOffset() const { return mOffset
.isSome(); }
528 uint32_t Offset() const {
529 if (mOffset
.isSome()) {
530 MOZ_ASSERT(mOffset
.isSome());
531 return mOffset
.value();
533 if (MOZ_UNLIKELY(!mParent
)) {
537 MOZ_ASSERT(mParent
->IsContainerNode(),
538 "If the container cannot have children, mOffset.isSome() should "
541 // We're referring after the last child. Fix offset now.
542 const_cast<SelfType
*>(this)->mOffset
= mozilla::Some(mParent
->Length());
543 return mOffset
.value();
545 MOZ_ASSERT(mChild
->GetParentNode() == mParent
);
547 if (mChild
== mParent
->GetFirstChild()) {
548 const_cast<SelfType
*>(this)->mOffset
= mozilla::Some(0u);
551 const_cast<SelfType
*>(this)->mOffset
= mParent
->ComputeIndexOf(mChild
);
552 MOZ_DIAGNOSTIC_ASSERT(mOffset
.isSome());
553 return mOffset
.valueOr(0u); // Avoid crash in Release/Beta
557 * Set() sets a point to aOffset or aChild.
558 * If it's set with aOffset, mChild is invalidated. If it's set with aChild,
559 * mOffset may be invalidated.
561 template <typename ContainerType
>
562 void Set(ContainerType
* aContainer
, uint32_t aOffset
) {
563 mParent
= aContainer
;
565 mOffset
= mozilla::Some(aOffset
);
566 mIsChildInitialized
= false;
567 mInterlinePosition
= InterlinePosition::Undefined
;
568 NS_ASSERTION(!mParent
|| mOffset
.value() <= mParent
->Length(),
569 "The offset is out of bounds");
571 template <typename ContainerType
, template <typename
> typename StrongPtr
>
572 void Set(const StrongPtr
<ContainerType
>& aContainer
, uint32_t aOffset
) {
573 Set(aContainer
.get(), aOffset
);
575 void Set(const nsINode
* aChild
) {
577 if (NS_WARN_IF(!aChild
->IsContent())) {
581 mParent
= aChild
->GetParentNode();
582 mChild
= const_cast<nsIContent
*>(aChild
->AsContent());
584 mIsChildInitialized
= true;
585 mInterlinePosition
= InterlinePosition::Undefined
;
589 * SetToEndOf() sets this to the end of aContainer. Then, mChild is always
590 * nullptr but marked as initialized and mOffset is always set.
592 template <typename ContainerType
>
593 MOZ_NEVER_INLINE_DEBUG
void SetToEndOf(const ContainerType
* aContainer
) {
594 MOZ_ASSERT(aContainer
);
595 mParent
= const_cast<ContainerType
*>(aContainer
);
597 mOffset
= mozilla::Some(mParent
->Length());
598 mIsChildInitialized
= true;
599 mInterlinePosition
= InterlinePosition::Undefined
;
601 template <typename ContainerType
, template <typename
> typename StrongPtr
>
602 MOZ_NEVER_INLINE_DEBUG
void SetToEndOf(
603 const StrongPtr
<ContainerType
>& aContainer
) {
604 SetToEndOf(aContainer
.get());
606 template <typename ContainerType
>
607 MOZ_NEVER_INLINE_DEBUG
static SelfType
AtEndOf(
608 const ContainerType
& aContainer
,
609 InterlinePosition aInterlinePosition
= InterlinePosition::Undefined
) {
611 point
.SetToEndOf(&aContainer
);
612 point
.mInterlinePosition
= aInterlinePosition
;
615 template <typename ContainerType
, template <typename
> typename StrongPtr
>
616 MOZ_NEVER_INLINE_DEBUG
static SelfType
AtEndOf(
617 const StrongPtr
<ContainerType
>& aContainer
,
618 InterlinePosition aInterlinePosition
= InterlinePosition::Undefined
) {
619 MOZ_ASSERT(aContainer
.get());
620 return AtEndOf(*aContainer
.get(), aInterlinePosition
);
624 * SetAfter() sets mChild to next sibling of aChild.
626 void SetAfter(const nsINode
* aChild
) {
628 nsIContent
* nextSibling
= aChild
->GetNextSibling();
633 nsINode
* parentNode
= aChild
->GetParentNode();
634 if (NS_WARN_IF(!parentNode
)) {
638 SetToEndOf(parentNode
);
640 template <typename ContainerType
>
641 static SelfType
After(
642 const ContainerType
& aContainer
,
643 InterlinePosition aInterlinePosition
= InterlinePosition::Undefined
) {
645 point
.SetAfter(&aContainer
);
646 point
.mInterlinePosition
= aInterlinePosition
;
649 template <typename ContainerType
, template <typename
> typename StrongPtr
>
650 MOZ_NEVER_INLINE_DEBUG
static SelfType
After(
651 const StrongPtr
<ContainerType
>& aContainer
,
652 InterlinePosition aInterlinePosition
= InterlinePosition::Undefined
) {
653 MOZ_ASSERT(aContainer
.get());
654 return After(*aContainer
.get(), aInterlinePosition
);
656 template <typename PT
, typename CT
>
657 MOZ_NEVER_INLINE_DEBUG
static SelfType
After(
658 const EditorDOMPointBase
<PT
, CT
>& aPoint
,
659 InterlinePosition aInterlinePosition
= InterlinePosition::Undefined
) {
660 MOZ_ASSERT(aPoint
.IsSet());
662 return After(*aPoint
.mChild
, aInterlinePosition
);
664 if (NS_WARN_IF(aPoint
.IsEndOfContainer())) {
667 auto point
= aPoint
.NextPoint().template To
<SelfType
>();
668 point
.mInterlinePosition
= aInterlinePosition
;
673 * ParentPoint() returns a point whose child is the container.
675 template <typename EditorDOMPointType
= SelfType
>
676 EditorDOMPointType
ParentPoint() const {
678 if (MOZ_UNLIKELY(!mParent
) || !mParent
->IsContent()) {
679 return EditorDOMPointType();
681 return EditorDOMPointType(ContainerAs
<nsIContent
>());
685 * NextPoint() and PreviousPoint() returns next/previous DOM point in
688 template <typename EditorDOMPointType
= SelfType
>
689 EditorDOMPointType
NextPoint() const {
690 NS_ASSERTION(!IsEndOfContainer(), "Should not be at end of the container");
691 auto result
= this->template To
<EditorDOMPointType
>();
692 result
.AdvanceOffset();
695 template <typename EditorDOMPointType
= SelfType
>
696 EditorDOMPointType
PreviousPoint() const {
697 NS_ASSERTION(!IsStartOfContainer(),
698 "Should not be at start of the container");
699 EditorDOMPointType result
= this->template To
<EditorDOMPointType
>();
700 result
.RewindOffset();
705 * Clear() makes the instance not point anywhere.
711 mIsChildInitialized
= false;
712 mInterlinePosition
= InterlinePosition::Undefined
;
716 * AdvanceOffset() tries to refer next sibling of mChild and/of next offset.
717 * If the container can have children and there is no next sibling or the
718 * offset reached the length of the container, this outputs warning and does
719 * nothing. So, callers need to check if there is next sibling which you
722 * @return true if there is a next DOM point to refer.
724 bool AdvanceOffset() {
725 if (NS_WARN_IF(!mParent
)) {
728 // If only mOffset is available, just compute the offset.
729 if ((mOffset
.isSome() && !mIsChildInitialized
) ||
730 !mParent
->IsContainerNode()) {
731 MOZ_ASSERT(mOffset
.isSome());
733 if (NS_WARN_IF(mOffset
.value() >= mParent
->Length())) {
734 // We're already referring the start of the container.
737 mOffset
= mozilla::Some(mOffset
.value() + 1);
738 mInterlinePosition
= InterlinePosition::Undefined
;
742 MOZ_ASSERT(mIsChildInitialized
);
743 MOZ_ASSERT(!mOffset
.isSome() || mOffset
.isSome());
744 if (NS_WARN_IF(!mParent
->HasChildren()) || NS_WARN_IF(!mChild
) ||
745 NS_WARN_IF(mOffset
.isSome() && mOffset
.value() >= mParent
->Length())) {
746 // We're already referring the end of the container (or outside).
750 if (mOffset
.isSome()) {
751 MOZ_ASSERT(mOffset
.isSome());
752 mOffset
= mozilla::Some(mOffset
.value() + 1);
754 mChild
= mChild
->GetNextSibling();
755 mInterlinePosition
= InterlinePosition::Undefined
;
760 * RewindOffset() tries to refer previous sibling of mChild and/or previous
761 * offset. If the container can have children and there is no next previous
762 * or the offset is 0, this outputs warning and does nothing. So, callers
763 * need to check if there is previous sibling which you need to refer.
765 * @return true if there is a previous DOM point to refer.
767 bool RewindOffset() {
768 if (NS_WARN_IF(!mParent
)) {
771 // If only mOffset is available, just compute the offset.
772 if ((mOffset
.isSome() && !mIsChildInitialized
) ||
773 !mParent
->IsContainerNode()) {
774 MOZ_ASSERT(mOffset
.isSome());
776 if (NS_WARN_IF(!mOffset
.value()) ||
777 NS_WARN_IF(mOffset
.value() > mParent
->Length())) {
778 // We're already referring the start of the container or
779 // the offset is invalid since perhaps, the offset was set before
780 // the last DOM tree change.
781 NS_ASSERTION(false, "Failed to rewind offset");
784 mOffset
= mozilla::Some(mOffset
.value() - 1);
785 mInterlinePosition
= InterlinePosition::Undefined
;
789 MOZ_ASSERT(mIsChildInitialized
);
790 MOZ_ASSERT(!mOffset
.isSome() || mOffset
.isSome());
791 if (NS_WARN_IF(!mParent
->HasChildren()) ||
792 NS_WARN_IF(mChild
&& !mChild
->GetPreviousSibling()) ||
793 NS_WARN_IF(mOffset
.isSome() && !mOffset
.value())) {
794 // We're already referring the start of the container (or the child has
795 // been moved from the container?).
799 nsIContent
* previousSibling
=
800 mChild
? mChild
->GetPreviousSibling() : mParent
->GetLastChild();
801 if (NS_WARN_IF(!previousSibling
)) {
802 // We're already referring the first child of the container.
806 if (mOffset
.isSome()) {
807 mOffset
= mozilla::Some(mOffset
.value() - 1);
809 mChild
= previousSibling
;
810 mInterlinePosition
= InterlinePosition::Undefined
;
815 * GetNonAnonymousSubtreePoint() returns a DOM point which is NOT in
816 * native-anonymous subtree. If the instance isn't in native-anonymous
817 * subtree, this returns same point. Otherwise, climbs up until finding
818 * non-native-anonymous parent and returns the point of it. I.e.,
819 * container is parent of the found non-anonymous-native node.
821 template <typename EditorDOMPointType
>
822 EditorDOMPointType
GetNonAnonymousSubtreePoint() const {
823 if (NS_WARN_IF(!IsSet())) {
824 return EditorDOMPointType();
826 if (!IsInNativeAnonymousSubtree()) {
827 return this->template To
<EditorDOMPointType
>();
830 for (parent
= mParent
->GetParentNode();
831 parent
&& parent
->IsInNativeAnonymousSubtree();
832 parent
= parent
->GetParentNode()) {
835 return EditorDOMPointType();
837 return EditorDOMPointType(parent
);
840 [[nodiscard
]] bool IsSet() const {
841 return mParent
&& (mIsChildInitialized
|| mOffset
.isSome());
844 [[nodiscard
]] bool IsSetAndValid() const {
850 (mChild
->GetParentNode() != mParent
|| mChild
->IsBeingRemoved())) {
853 if (mOffset
.isSome() && mOffset
.value() > mParent
->Length()) {
859 [[nodiscard
]] bool IsInComposedDoc() const {
860 return IsSet() && mParent
->IsInComposedDoc();
863 [[nodiscard
]] bool IsSetAndValidInComposedDoc() const {
864 return IsInComposedDoc() && IsSetAndValid();
867 bool IsStartOfContainer() const {
868 // If we're referring the first point in the container:
869 // If mParent is not a container like a text node, mOffset is 0.
870 // If mChild is initialized and it's first child of mParent.
871 // If mChild isn't initialized and the offset is 0.
872 if (NS_WARN_IF(!mParent
)) {
875 if (!mParent
->IsContainerNode()) {
876 return !mOffset
.value();
878 if (mIsChildInitialized
) {
879 if (mParent
->GetFirstChild() == mChild
) {
880 NS_WARNING_ASSERTION(!mOffset
.isSome() || !mOffset
.value(),
881 "If mOffset was initialized, it should be 0");
884 NS_WARNING_ASSERTION(!mOffset
.isSome() || mParent
->GetChildAt_Deprecated(
885 mOffset
.value()) == mChild
,
886 "mOffset and mChild are mismatched");
889 MOZ_ASSERT(mOffset
.isSome());
890 return !mOffset
.value();
893 bool IsEndOfContainer() const {
894 // If we're referring after the last point of the container:
895 // If mParent is not a container like text node, mOffset is same as the
896 // length of the container.
897 // If mChild is initialized and it's nullptr.
898 // If mChild isn't initialized and mOffset is same as the length of the
900 if (NS_WARN_IF(!mParent
)) {
903 if (!mParent
->IsContainerNode()) {
904 return mOffset
.value() == mParent
->Length();
906 if (mIsChildInitialized
) {
908 NS_WARNING_ASSERTION(
909 !mOffset
.isSome() || mOffset
.value() == mParent
->Length(),
910 "If mOffset was initialized, it should be length of the container");
913 NS_WARNING_ASSERTION(!mOffset
.isSome() || mParent
->GetChildAt_Deprecated(
914 mOffset
.value()) == mChild
,
915 "mOffset and mChild are mismatched");
918 MOZ_ASSERT(mOffset
.isSome());
919 return mOffset
.value() == mParent
->Length();
923 * IsAtLastContent() returns true when it refers last child of the container
924 * or last character offset of text node.
926 bool IsAtLastContent() const {
927 if (NS_WARN_IF(!mParent
)) {
930 if (mParent
->IsContainerNode() && mOffset
.isSome()) {
931 return mOffset
.value() == mParent
->Length() - 1;
933 if (mIsChildInitialized
) {
934 if (mChild
&& mChild
== mParent
->GetLastChild()) {
935 NS_WARNING_ASSERTION(
936 !mOffset
.isSome() || mOffset
.value() == mParent
->Length() - 1,
937 "If mOffset was initialized, it should be length - 1 of the "
941 NS_WARNING_ASSERTION(!mOffset
.isSome() || mParent
->GetChildAt_Deprecated(
942 mOffset
.value()) == mChild
,
943 "mOffset and mChild are mismatched");
946 MOZ_ASSERT(mOffset
.isSome());
947 return mOffset
.value() == mParent
->Length() - 1;
950 bool IsBRElementAtEndOfContainer() const {
951 if (NS_WARN_IF(!mParent
)) {
954 if (!mParent
->IsContainerNode()) {
957 const_cast<SelfType
*>(this)->EnsureChild();
958 if (!mChild
|| mChild
->GetNextSibling()) {
961 return mChild
->IsHTMLElement(nsGkAtoms::br
);
965 * Return a point in text node if "this" points around a text node.
966 * EditorDOMPointType can always be EditorDOMPoint or EditorRawDOMPoint,
967 * but EditorDOMPointInText or EditorRawDOMPointInText is also available
968 * only when "this type" is one of them.
969 * If the point is in the anonymous <div> of a TextEditor, use
970 * TextEditor::FindBetterInsertionPoint() instead.
972 template <typename EditorDOMPointType
>
973 EditorDOMPointType
GetPointInTextNodeIfPointingAroundTextNode() const {
974 if (NS_WARN_IF(!IsSet()) || !mParent
->HasChildren()) {
975 return To
<EditorDOMPointType
>();
977 if (IsStartOfContainer()) {
978 if (auto* firstTextChild
=
979 dom::Text::FromNode(mParent
->GetFirstChild())) {
980 return EditorDOMPointType(firstTextChild
, 0u);
982 return To
<EditorDOMPointType
>();
984 if (auto* previousSiblingChild
= dom::Text::FromNodeOrNull(
985 GetPreviousSiblingOfChildAs
<dom::Text
>())) {
986 return EditorDOMPointType::AtEndOf(*previousSiblingChild
);
988 if (auto* child
= dom::Text::FromNodeOrNull(GetChildAs
<dom::Text
>())) {
989 return EditorDOMPointType(child
, 0u);
991 return To
<EditorDOMPointType
>();
994 template <typename A
, typename B
>
995 EditorDOMPointBase
& operator=(const RangeBoundaryBase
<A
, B
>& aOther
) {
996 mParent
= aOther
.mParent
;
997 mChild
= aOther
.mRef
? aOther
.mRef
->GetNextSibling()
998 : (aOther
.mParent
&& aOther
.mParent
->IsContainerNode()
999 ? aOther
.mParent
->GetFirstChild()
1001 mOffset
= aOther
.mOffset
;
1002 mIsChildInitialized
=
1003 aOther
.mRef
|| (aOther
.mParent
&& !aOther
.mParent
->IsContainerNode()) ||
1004 (aOther
.mOffset
.isSome() && !aOther
.mOffset
.value());
1005 mInterlinePosition
= InterlinePosition::Undefined
;
1009 template <typename EditorDOMPointType
>
1010 constexpr EditorDOMPointType
To() const {
1011 // XXX Cannot specialize this method due to implicit instantiatation caused
1012 // by the inline CC functions below.
1013 if (std::is_same
<SelfType
, EditorDOMPointType
>::value
) {
1014 return reinterpret_cast<const EditorDOMPointType
&>(*this);
1016 EditorDOMPointType result
;
1017 result
.mParent
= mParent
;
1018 result
.mChild
= mChild
;
1019 result
.mOffset
= mOffset
;
1020 result
.mIsChildInitialized
= mIsChildInitialized
;
1021 result
.mInterlinePosition
= mInterlinePosition
;
1026 * Don't compare mInterlinePosition. If it's required to check, perhaps,
1027 * another compare operator like `===` should be created.
1029 template <typename A
, typename B
>
1030 bool operator==(const EditorDOMPointBase
<A
, B
>& aOther
) const {
1031 if (mParent
!= aOther
.mParent
) {
1035 if (mOffset
.isSome() && aOther
.mOffset
.isSome()) {
1036 // If both mOffset are set, we need to compare both mRef too because
1037 // the relation of mRef and mOffset have already broken by DOM tree
1039 if (mOffset
!= aOther
.mOffset
) {
1042 if (mChild
== aOther
.mChild
) {
1045 if (NS_WARN_IF(mIsChildInitialized
&& aOther
.mIsChildInitialized
)) {
1046 // In this case, relation between mChild and mOffset of one of or both
1047 // of them doesn't match with current DOM tree since the DOM tree might
1048 // have been changed after computing mChild or mOffset.
1051 // If one of mChild hasn't been computed yet, we should compare them only
1052 // with mOffset. Perhaps, we shouldn't copy mChild from non-nullptr one
1053 // to the other since if we copy it here, it may be unexpected behavior
1054 // for some callers.
1058 MOZ_ASSERT(mIsChildInitialized
|| aOther
.mIsChildInitialized
);
1060 if (mOffset
.isSome() && !mIsChildInitialized
&& !aOther
.mOffset
.isSome() &&
1061 aOther
.mIsChildInitialized
) {
1062 // If this has only mOffset and the other has only mChild, this needs to
1063 // compute mChild now.
1064 const_cast<SelfType
*>(this)->EnsureChild();
1065 return mChild
== aOther
.mChild
;
1068 if (!mOffset
.isSome() && mIsChildInitialized
&& aOther
.mOffset
.isSome() &&
1069 !aOther
.mIsChildInitialized
) {
1070 // If this has only mChild and the other has only mOffset, the other needs
1071 // to compute mChild now.
1072 const_cast<EditorDOMPointBase
<A
, B
>&>(aOther
).EnsureChild();
1073 return mChild
== aOther
.mChild
;
1076 // If mOffset of one of them hasn't been computed from mChild yet, we should
1077 // compare only with mChild. Perhaps, we shouldn't copy mOffset from being
1078 // some one to not being some one since if we copy it here, it may be
1079 // unexpected behavior for some callers.
1080 return mChild
== aOther
.mChild
;
1083 template <typename A
, typename B
>
1084 bool operator==(const RangeBoundaryBase
<A
, B
>& aOther
) const {
1085 // TODO: Optimize this with directly comparing with RangeBoundaryBase
1087 return *this == SelfType(aOther
);
1090 template <typename A
, typename B
>
1091 bool operator!=(const EditorDOMPointBase
<A
, B
>& aOther
) const {
1092 return !(*this == aOther
);
1095 template <typename A
, typename B
>
1096 bool operator!=(const RangeBoundaryBase
<A
, B
>& aOther
) const {
1097 return !(*this == aOther
);
1101 * This operator should be used if API of other modules take RawRangeBoundary,
1102 * e.g., methods of Selection and nsRange.
1104 operator const RawRangeBoundary() const { return ToRawRangeBoundary(); }
1105 const RawRangeBoundary
ToRawRangeBoundary() const {
1106 if (!IsSet() || NS_WARN_IF(!mIsChildInitialized
&& !mOffset
.isSome())) {
1107 return RawRangeBoundary();
1109 if (!mParent
->IsContainerNode()) {
1110 MOZ_ASSERT(mOffset
.value() <= mParent
->Length());
1111 // If the container is a data node like a text node, we need to create
1112 // RangeBoundaryBase instance only with mOffset because mChild is always
1114 return RawRangeBoundary(mParent
, mOffset
.value());
1116 if (mIsChildInitialized
&& mOffset
.isSome()) {
1117 // If we've already set both child and offset, we should create
1118 // RangeBoundary with offset after validation.
1121 MOZ_ASSERT(mParent
== mChild
->GetParentNode());
1122 MOZ_ASSERT(mParent
->GetChildAt_Deprecated(mOffset
.value()) == mChild
);
1124 MOZ_ASSERT(mParent
->Length() == mOffset
.value());
1126 #endif // #ifdef DEBUG
1127 return RawRangeBoundary(mParent
, mOffset
.value());
1129 // Otherwise, we should create RangeBoundaryBase only with available
1131 if (mOffset
.isSome()) {
1132 return RawRangeBoundary(mParent
, mOffset
.value());
1135 return RawRangeBoundary(mParent
, mChild
->GetPreviousSibling());
1137 return RawRangeBoundary(mParent
, mParent
->GetLastChild());
1140 already_AddRefed
<nsRange
> CreateCollapsedRange(ErrorResult
& aRv
) const {
1141 const RawRangeBoundary boundary
= ToRawRangeBoundary();
1142 RefPtr
<nsRange
> range
= nsRange::Create(boundary
, boundary
, aRv
);
1143 if (MOZ_UNLIKELY(aRv
.Failed() || !range
)) {
1146 return range
.forget();
1149 EditorDOMPointInText
GetAsInText() const {
1150 return IsInTextNode() ? EditorDOMPointInText(ContainerAs
<dom::Text
>(),
1151 Offset(), mInterlinePosition
)
1152 : EditorDOMPointInText();
1154 MOZ_NEVER_INLINE_DEBUG EditorDOMPointInText
AsInText() const {
1155 MOZ_ASSERT(IsInTextNode());
1156 return EditorDOMPointInText(ContainerAs
<dom::Text
>(), Offset(),
1157 mInterlinePosition
);
1160 template <typename A
, typename B
>
1161 bool IsBefore(const EditorDOMPointBase
<A
, B
>& aOther
) const {
1162 if (!IsSetAndValid() || !aOther
.IsSetAndValid()) {
1165 Maybe
<int32_t> comp
= nsContentUtils::ComparePoints(
1166 ToRawRangeBoundary(), aOther
.ToRawRangeBoundary());
1167 return comp
.isSome() && comp
.value() == -1;
1170 template <typename A
, typename B
>
1171 bool EqualsOrIsBefore(const EditorDOMPointBase
<A
, B
>& aOther
) const {
1172 if (!IsSetAndValid() || !aOther
.IsSetAndValid()) {
1175 Maybe
<int32_t> comp
= nsContentUtils::ComparePoints(
1176 ToRawRangeBoundary(), aOther
.ToRawRangeBoundary());
1177 return comp
.isSome() && comp
.value() <= 0;
1180 friend std::ostream
& operator<<(std::ostream
& aStream
,
1181 const SelfType
& aDOMPoint
) {
1182 aStream
<< "{ mParent=" << aDOMPoint
.GetContainer();
1183 if (aDOMPoint
.mParent
) {
1184 aStream
<< " (" << *aDOMPoint
.mParent
1185 << ", Length()=" << aDOMPoint
.mParent
->Length() << ")";
1187 aStream
<< ", mChild=" << static_cast<nsIContent
*>(aDOMPoint
.mChild
);
1188 if (aDOMPoint
.mChild
) {
1189 aStream
<< " (" << *aDOMPoint
.mChild
<< ")";
1191 aStream
<< ", mOffset=" << aDOMPoint
.mOffset
<< ", mIsChildInitialized="
1192 << (aDOMPoint
.mIsChildInitialized
? "true" : "false")
1193 << ", mInterlinePosition=" << aDOMPoint
.mInterlinePosition
<< " }";
1198 void EnsureChild() {
1199 if (mIsChildInitialized
) {
1203 MOZ_ASSERT(!mOffset
.isSome());
1206 MOZ_ASSERT(mOffset
.isSome());
1207 MOZ_ASSERT(mOffset
.value() <= mParent
->Length());
1208 mIsChildInitialized
= true;
1209 if (!mParent
->IsContainerNode()) {
1212 mChild
= mParent
->GetChildAt_Deprecated(mOffset
.value());
1213 MOZ_ASSERT(mChild
|| mOffset
.value() == mParent
->Length());
1216 ParentType mParent
= nullptr;
1217 ChildType mChild
= nullptr;
1219 Maybe
<uint32_t> mOffset
;
1220 InterlinePosition mInterlinePosition
= InterlinePosition::Undefined
;
1221 bool mIsChildInitialized
= false;
1223 template <typename PT
, typename CT
>
1224 friend class EditorDOMPointBase
;
1226 friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback
&,
1227 EditorDOMPoint
&, const char*,
1229 friend void ImplCycleCollectionUnlink(EditorDOMPoint
&);
1232 inline void ImplCycleCollectionUnlink(EditorDOMPoint
& aField
) {
1233 ImplCycleCollectionUnlink(aField
.mParent
);
1234 ImplCycleCollectionUnlink(aField
.mChild
);
1237 inline void ImplCycleCollectionTraverse(
1238 nsCycleCollectionTraversalCallback
& aCallback
, EditorDOMPoint
& aField
,
1239 const char* aName
, uint32_t aFlags
) {
1240 ImplCycleCollectionTraverse(aCallback
, aField
.mParent
, "mParent", 0);
1241 ImplCycleCollectionTraverse(aCallback
, aField
.mChild
, "mChild", 0);
1245 * EditorDOMRangeBase class stores a pair of same EditorDOMPointBase type.
1246 * The instance must be created with valid DOM points and start must be
1247 * before or same as end.
1249 #define NS_INSTANTIATE_EDITOR_DOM_RANGE_METHOD(aResultType, aMethodName, ...) \
1250 template aResultType EditorDOMRange::aMethodName(__VA_ARGS__); \
1251 template aResultType EditorRawDOMRange::aMethodName(__VA_ARGS__); \
1252 template aResultType EditorDOMRangeInTexts::aMethodName(__VA_ARGS__); \
1253 template aResultType EditorRawDOMRangeInTexts::aMethodName(__VA_ARGS__)
1255 #define NS_INSTANTIATE_EDITOR_DOM_RANGE_CONST_METHOD(aResultType, aMethodName, \
1257 template aResultType EditorDOMRange::aMethodName(__VA_ARGS__) const; \
1258 template aResultType EditorRawDOMRange::aMethodName(__VA_ARGS__) const; \
1259 template aResultType EditorDOMRangeInTexts::aMethodName(__VA_ARGS__) const; \
1260 template aResultType EditorRawDOMRangeInTexts::aMethodName(__VA_ARGS__) const
1261 template <typename EditorDOMPointType
>
1262 class EditorDOMRangeBase final
{
1263 using SelfType
= EditorDOMRangeBase
<EditorDOMPointType
>;
1266 using PointType
= EditorDOMPointType
;
1268 EditorDOMRangeBase() = default;
1269 template <typename PT
, typename CT
>
1270 explicit EditorDOMRangeBase(const EditorDOMPointBase
<PT
, CT
>& aStart
)
1271 : mStart(aStart
), mEnd(aStart
) {
1272 MOZ_ASSERT(!mStart
.IsSet() || mStart
.IsSetAndValid());
1274 template <typename StartPointType
, typename EndPointType
>
1275 explicit EditorDOMRangeBase(const StartPointType
& aStart
,
1276 const EndPointType
& aEnd
)
1277 : mStart(aStart
.template To
<PointType
>()),
1278 mEnd(aEnd
.template To
<PointType
>()) {
1279 MOZ_ASSERT_IF(mStart
.IsSet(), mStart
.IsSetAndValid());
1280 MOZ_ASSERT_IF(mEnd
.IsSet(), mEnd
.IsSetAndValid());
1281 MOZ_ASSERT_IF(mStart
.IsSet() && mEnd
.IsSet(),
1282 mStart
.EqualsOrIsBefore(mEnd
));
1284 explicit EditorDOMRangeBase(EditorDOMPointType
&& aStart
,
1285 EditorDOMPointType
&& aEnd
)
1286 : mStart(std::move(aStart
)), mEnd(std::move(aEnd
)) {
1287 MOZ_ASSERT_IF(mStart
.IsSet(), mStart
.IsSetAndValid());
1288 MOZ_ASSERT_IF(mEnd
.IsSet(), mEnd
.IsSetAndValid());
1289 MOZ_ASSERT_IF(mStart
.IsSet() && mEnd
.IsSet(),
1290 mStart
.EqualsOrIsBefore(mEnd
));
1292 template <typename OtherPointType
>
1293 explicit EditorDOMRangeBase(const EditorDOMRangeBase
<OtherPointType
>& aOther
)
1294 : mStart(aOther
.StartRef().template To
<PointType
>()),
1295 mEnd(aOther
.EndRef().template To
<PointType
>()) {
1296 MOZ_ASSERT_IF(mStart
.IsSet(), mStart
.IsSetAndValid());
1297 MOZ_ASSERT_IF(mEnd
.IsSet(), mEnd
.IsSetAndValid());
1298 MOZ_ASSERT(mStart
.IsSet() == mEnd
.IsSet());
1300 explicit EditorDOMRangeBase(const dom::AbstractRange
& aRange
)
1301 : mStart(aRange
.StartRef()), mEnd(aRange
.EndRef()) {
1302 MOZ_ASSERT_IF(mStart
.IsSet(), mStart
.IsSetAndValid());
1303 MOZ_ASSERT_IF(mEnd
.IsSet(), mEnd
.IsSetAndValid());
1304 MOZ_ASSERT_IF(mStart
.IsSet() && mEnd
.IsSet(),
1305 mStart
.EqualsOrIsBefore(mEnd
));
1308 template <typename MaybeOtherPointType
>
1309 void SetStart(const MaybeOtherPointType
& aStart
) {
1310 mStart
= aStart
.template To
<PointType
>();
1312 void SetStart(PointType
&& aStart
) { mStart
= std::move(aStart
); }
1313 template <typename MaybeOtherPointType
>
1314 void SetEnd(const MaybeOtherPointType
& aEnd
) {
1315 mEnd
= aEnd
.template To
<PointType
>();
1317 void SetEnd(PointType
&& aEnd
) { mEnd
= std::move(aEnd
); }
1318 template <typename StartPointType
, typename EndPointType
>
1319 void SetStartAndEnd(const StartPointType
& aStart
, const EndPointType
& aEnd
) {
1320 MOZ_ASSERT_IF(aStart
.IsSet() && aEnd
.IsSet(),
1321 aStart
.EqualsOrIsBefore(aEnd
));
1322 mStart
= aStart
.template To
<PointType
>();
1323 mEnd
= aEnd
.template To
<PointType
>();
1325 template <typename StartPointType
>
1326 void SetStartAndEnd(const StartPointType
& aStart
, PointType
&& aEnd
) {
1327 MOZ_ASSERT_IF(aStart
.IsSet() && aEnd
.IsSet(),
1328 aStart
.EqualsOrIsBefore(aEnd
));
1329 mStart
= aStart
.template To
<PointType
>();
1330 mEnd
= std::move(aEnd
);
1332 template <typename EndPointType
>
1333 void SetStartAndEnd(PointType
&& aStart
, const EndPointType
& aEnd
) {
1334 MOZ_ASSERT_IF(aStart
.IsSet() && aEnd
.IsSet(),
1335 aStart
.EqualsOrIsBefore(aEnd
));
1336 mStart
= std::move(aStart
);
1337 mEnd
= aEnd
.template To
<PointType
>();
1339 void SetStartAndEnd(PointType
&& aStart
, PointType
&& aEnd
) {
1340 MOZ_ASSERT_IF(aStart
.IsSet() && aEnd
.IsSet(),
1341 aStart
.EqualsOrIsBefore(aEnd
));
1342 mStart
= std::move(aStart
);
1343 mEnd
= std::move(aEnd
);
1350 const PointType
& StartRef() const { return mStart
; }
1351 const PointType
& EndRef() const { return mEnd
; }
1353 bool Collapsed() const {
1354 MOZ_ASSERT(IsPositioned());
1355 return mStart
== mEnd
;
1357 bool IsPositioned() const { return mStart
.IsSet() && mEnd
.IsSet(); }
1358 bool IsPositionedAndValid() const {
1359 return mStart
.IsSetAndValid() && mEnd
.IsSetAndValid() &&
1360 mStart
.EqualsOrIsBefore(mEnd
);
1362 template <typename OtherPointType
>
1363 MOZ_NEVER_INLINE_DEBUG
bool Contains(const OtherPointType
& aPoint
) const {
1364 MOZ_ASSERT(aPoint
.IsSetAndValid());
1365 return IsPositioned() && aPoint
.IsSet() &&
1366 mStart
.EqualsOrIsBefore(aPoint
) && aPoint
.IsBefore(mEnd
);
1368 [[nodiscard
]] nsINode
* GetClosestCommonInclusiveAncestor() const;
1369 bool InSameContainer() const {
1370 MOZ_ASSERT(IsPositioned());
1371 return IsPositioned() && mStart
.GetContainer() == mEnd
.GetContainer();
1373 bool InAdjacentSiblings() const {
1374 MOZ_ASSERT(IsPositioned());
1375 return IsPositioned() &&
1376 mStart
.GetContainer()->GetNextSibling() == mEnd
.GetContainer();
1378 bool IsInContentNodes() const {
1379 MOZ_ASSERT(IsPositioned());
1380 return IsPositioned() && mStart
.IsInContentNode() && mEnd
.IsInContentNode();
1382 bool IsInTextNodes() const {
1383 MOZ_ASSERT(IsPositioned());
1384 return IsPositioned() && mStart
.IsInTextNode() && mEnd
.IsInTextNode();
1386 template <typename OtherRangeType
>
1387 bool operator==(const OtherRangeType
& aOther
) const {
1388 return (!IsPositioned() && !aOther
.IsPositioned()) ||
1389 (mStart
== aOther
.mStart
&& mEnd
== aOther
.mEnd
);
1391 template <typename OtherRangeType
>
1392 bool operator!=(const OtherRangeType
& aOther
) const {
1393 return !(*this == aOther
);
1396 EditorDOMRangeInTexts
GetAsInTexts() const {
1397 return IsInTextNodes()
1398 ? EditorDOMRangeInTexts(mStart
.AsInText(), mEnd
.AsInText())
1399 : EditorDOMRangeInTexts();
1401 MOZ_NEVER_INLINE_DEBUG EditorDOMRangeInTexts
AsInTexts() const {
1402 MOZ_ASSERT(IsInTextNodes());
1403 return EditorDOMRangeInTexts(mStart
.AsInText(), mEnd
.AsInText());
1406 bool EnsureNotInNativeAnonymousSubtree() {
1407 if (mStart
.IsInNativeAnonymousSubtree()) {
1408 nsIContent
* parent
= nullptr;
1409 for (parent
= mStart
.template ContainerAs
<nsIContent
>()
1410 ->GetClosestNativeAnonymousSubtreeRootParentOrHost();
1411 parent
&& parent
->IsInNativeAnonymousSubtree();
1413 parent
->GetClosestNativeAnonymousSubtreeRootParentOrHost()) {
1415 if (MOZ_UNLIKELY(!parent
)) {
1420 if (mEnd
.IsInNativeAnonymousSubtree()) {
1421 nsIContent
* parent
= nullptr;
1422 for (parent
= mEnd
.template ContainerAs
<nsIContent
>()
1423 ->GetClosestNativeAnonymousSubtreeRootParentOrHost();
1424 parent
&& parent
->IsInNativeAnonymousSubtree();
1426 parent
->GetClosestNativeAnonymousSubtreeRootParentOrHost()) {
1428 if (MOZ_UNLIKELY(!parent
)) {
1431 mEnd
.SetAfter(parent
);
1436 already_AddRefed
<nsRange
> CreateRange(ErrorResult
& aRv
) const {
1437 RefPtr
<nsRange
> range
= nsRange::Create(mStart
.ToRawRangeBoundary(),
1438 mEnd
.ToRawRangeBoundary(), aRv
);
1439 if (MOZ_UNLIKELY(aRv
.Failed() || !range
)) {
1442 return range
.forget();
1445 friend std::ostream
& operator<<(std::ostream
& aStream
,
1446 const SelfType
& aRange
) {
1447 if (aRange
.Collapsed()) {
1448 aStream
<< "{ mStart=mEnd=" << aRange
.mStart
<< " }";
1450 aStream
<< "{ mStart=" << aRange
.mStart
<< ", mEnd=" << aRange
.mEnd
1457 EditorDOMPointType mStart
;
1458 EditorDOMPointType mEnd
;
1460 friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback
&,
1461 EditorDOMRange
&, const char*,
1463 friend void ImplCycleCollectionUnlink(EditorDOMRange
&);
1466 inline void ImplCycleCollectionUnlink(EditorDOMRange
& aField
) {
1467 ImplCycleCollectionUnlink(aField
.mStart
);
1468 ImplCycleCollectionUnlink(aField
.mEnd
);
1471 inline void ImplCycleCollectionTraverse(
1472 nsCycleCollectionTraversalCallback
& aCallback
, EditorDOMRange
& aField
,
1473 const char* aName
, uint32_t aFlags
) {
1474 ImplCycleCollectionTraverse(aCallback
, aField
.mStart
, "mStart", 0);
1475 ImplCycleCollectionTraverse(aCallback
, aField
.mEnd
, "mEnd", 0);
1479 * AutoEditorDOMPointOffsetInvalidator is useful if DOM tree will be changed
1480 * when EditorDOMPoint instance is available and keeps referring same child
1483 * This class automatically guarantees that given EditorDOMPoint instance
1484 * stores the child node and invalidates its offset when the instance is
1485 * destroyed. Additionally, users of this class can invalidate the offset
1486 * manually when they need.
1488 class MOZ_STACK_CLASS AutoEditorDOMPointOffsetInvalidator final
{
1490 AutoEditorDOMPointOffsetInvalidator() = delete;
1491 AutoEditorDOMPointOffsetInvalidator(
1492 const AutoEditorDOMPointOffsetInvalidator
&) = delete;
1493 AutoEditorDOMPointOffsetInvalidator(AutoEditorDOMPointOffsetInvalidator
&&) =
1495 const AutoEditorDOMPointOffsetInvalidator
& operator=(
1496 const AutoEditorDOMPointOffsetInvalidator
&) = delete;
1497 explicit AutoEditorDOMPointOffsetInvalidator(EditorDOMPoint
& aPoint
)
1498 : mPoint(aPoint
), mCanceled(false) {
1499 MOZ_ASSERT(aPoint
.IsSetAndValid());
1500 MOZ_ASSERT(mPoint
.CanContainerHaveChildren());
1501 mChild
= mPoint
.GetChild();
1504 ~AutoEditorDOMPointOffsetInvalidator() {
1511 * Manually, invalidate offset of the given point.
1513 void InvalidateOffset() {
1517 // If the point referred after the last child, let's keep referring
1518 // after current last node of the old container.
1519 mPoint
.SetToEndOf(mPoint
.GetContainer());
1524 * After calling Cancel(), mPoint won't be modified by the destructor.
1526 void Cancel() { mCanceled
= true; }
1529 EditorDOMPoint
& mPoint
;
1530 // Needs to store child node by ourselves because EditorDOMPoint stores
1531 // child node with mRef which is previous sibling of current child node.
1532 // Therefore, we cannot keep referring it if it's first child.
1533 nsCOMPtr
<nsIContent
> mChild
;
1538 class MOZ_STACK_CLASS AutoEditorDOMRangeOffsetsInvalidator final
{
1540 explicit AutoEditorDOMRangeOffsetsInvalidator(EditorDOMRange
& aRange
)
1541 : mStartInvalidator(const_cast<EditorDOMPoint
&>(aRange
.StartRef())),
1542 mEndInvalidator(const_cast<EditorDOMPoint
&>(aRange
.EndRef())) {}
1544 void InvalidateOffsets() {
1545 mStartInvalidator
.InvalidateOffset();
1546 mEndInvalidator
.InvalidateOffset();
1550 mStartInvalidator
.Cancel();
1551 mEndInvalidator
.Cancel();
1555 AutoEditorDOMPointOffsetInvalidator mStartInvalidator
;
1556 AutoEditorDOMPointOffsetInvalidator mEndInvalidator
;
1560 * AutoEditorDOMPointChildInvalidator is useful if DOM tree will be changed
1561 * when EditorDOMPoint instance is available and keeps referring same container
1564 * This class automatically guarantees that given EditorDOMPoint instance
1565 * stores offset and invalidates its child node when the instance is destroyed.
1566 * Additionally, users of this class can invalidate the child manually when
1569 class MOZ_STACK_CLASS AutoEditorDOMPointChildInvalidator final
{
1571 AutoEditorDOMPointChildInvalidator() = delete;
1572 AutoEditorDOMPointChildInvalidator(
1573 const AutoEditorDOMPointChildInvalidator
&) = delete;
1574 AutoEditorDOMPointChildInvalidator(AutoEditorDOMPointChildInvalidator
&&) =
1576 const AutoEditorDOMPointChildInvalidator
& operator=(
1577 const AutoEditorDOMPointChildInvalidator
&) = delete;
1578 explicit AutoEditorDOMPointChildInvalidator(EditorDOMPoint
& aPoint
)
1579 : mPoint(aPoint
), mCanceled(false) {
1580 MOZ_ASSERT(aPoint
.IsSetAndValid());
1581 Unused
<< mPoint
.Offset();
1584 ~AutoEditorDOMPointChildInvalidator() {
1591 * Manually, invalidate child of the given point.
1593 void InvalidateChild() { mPoint
.Set(mPoint
.GetContainer(), mPoint
.Offset()); }
1596 * After calling Cancel(), mPoint won't be modified by the destructor.
1598 void Cancel() { mCanceled
= true; }
1601 EditorDOMPoint
& mPoint
;
1606 class MOZ_STACK_CLASS AutoEditorDOMRangeChildrenInvalidator final
{
1608 explicit AutoEditorDOMRangeChildrenInvalidator(EditorDOMRange
& aRange
)
1609 : mStartInvalidator(const_cast<EditorDOMPoint
&>(aRange
.StartRef())),
1610 mEndInvalidator(const_cast<EditorDOMPoint
&>(aRange
.EndRef())) {}
1612 void InvalidateChildren() {
1613 mStartInvalidator
.InvalidateChild();
1614 mEndInvalidator
.InvalidateChild();
1618 mStartInvalidator
.Cancel();
1619 mEndInvalidator
.Cancel();
1623 AutoEditorDOMPointChildInvalidator mStartInvalidator
;
1624 AutoEditorDOMPointChildInvalidator mEndInvalidator
;
1627 } // namespace mozilla
1629 #endif // #ifndef mozilla_EditorDOMPoint_h