no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / editor / libeditor / EditorDOMPoint.h
blob907b02f059e10bfb37a59af5021afbc317f0f5ef
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"
19 #include "nsAtom.h"
20 #include "nsCOMPtr.h"
21 #include "nsContentUtils.h"
22 #include "nsCRT.h"
23 #include "nsGkAtoms.h"
24 #include "nsIContent.h"
25 #include "nsINode.h"
26 #include "nsStyledElement.h"
28 #include <type_traits>
30 namespace mozilla {
32 /**
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
50 * different:
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
58 * similarly:
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, \
84 ...) \
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( \
97 aMethodName, ...) \
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>;
107 public:
108 using InterlinePosition = dom::Selection::InterlinePosition;
110 EditorDOMPointBase() = default;
112 template <typename ContainerType>
113 EditorDOMPointBase(
114 const ContainerType* aContainer, uint32_t aOffset,
115 InterlinePosition aInterlinePosition = InterlinePosition::Undefined)
116 : mParent(const_cast<ContainerType*>(aContainer)),
117 mChild(nullptr),
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");
123 if (!mParent) {
124 mOffset.reset();
128 template <typename ContainerType, template <typename> typename StrongPtr>
129 EditorDOMPointBase(
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>
135 EditorDOMPointBase(
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()
149 : nullptr),
150 mChild(aPointedNode && aPointedNode->IsContent()
151 ? const_cast<nsIContent*>(aPointedNode->AsContent())
152 : nullptr),
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");
161 EditorDOMPointBase(
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()
183 : nullptr)),
184 mOffset(aOther.mOffset),
185 mIsChildInitialized(aOther.mRef || (aOther.mOffset.isSome() &&
186 !aOther.mOffset.value())) {}
188 void SetInterlinePosition(InterlinePosition aInterlinePosition) {
189 MOZ_ASSERT(IsSet());
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
209 * nor wrong cast.
211 template <typename ContentNodeType>
212 ContentNodeType* ContainerAs() const {
213 MOZ_ASSERT(mParent);
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)) {
236 return nullptr;
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
245 * false.
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
258 * nsIContent.
260 bool IsInContentNode() const { return mParent && mParent->IsContent(); }
263 * IsInDataNode() returns true if the container node is a data node including
264 * text node.
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()) {
305 return nullptr;
307 if (mIsChildInitialized) {
308 return mChild;
310 // Fix child node now.
311 const_cast<SelfType*>(this)->EnsureChild();
312 return mChild;
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()) {
332 return GetChild();
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>();
345 return GetChild();
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()) {
357 return nullptr;
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.
366 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 {
378 MOZ_ASSERT(IsSet());
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()) {
392 return nullptr;
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.
401 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 {
413 MOZ_ASSERT(IsSet());
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()
450 const {
451 MOZ_ASSERT(IsSetAndValid());
452 MOZ_ASSERT(!IsEndOfContainer());
453 return ContainerAs<dom::Text>()
454 ->TextFragment()
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>()
461 ->TextFragment()
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)) {
534 MOZ_ASSERT(!mChild);
535 return 0u;
537 MOZ_ASSERT(mParent->IsContainerNode(),
538 "If the container cannot have children, mOffset.isSome() should "
539 "be true");
540 if (!mChild) {
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);
546 // Fix offset now.
547 if (mChild == mParent->GetFirstChild()) {
548 const_cast<SelfType*>(this)->mOffset = mozilla::Some(0u);
549 return 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;
564 mChild = nullptr;
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) {
576 MOZ_ASSERT(aChild);
577 if (NS_WARN_IF(!aChild->IsContent())) {
578 Clear();
579 return;
581 mParent = aChild->GetParentNode();
582 mChild = const_cast<nsIContent*>(aChild->AsContent());
583 mOffset.reset();
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);
596 mChild = nullptr;
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) {
610 SelfType point;
611 point.SetToEndOf(&aContainer);
612 point.mInterlinePosition = aInterlinePosition;
613 return point;
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) {
627 MOZ_ASSERT(aChild);
628 nsIContent* nextSibling = aChild->GetNextSibling();
629 if (nextSibling) {
630 Set(nextSibling);
631 return;
633 nsINode* parentNode = aChild->GetParentNode();
634 if (NS_WARN_IF(!parentNode)) {
635 Clear();
636 return;
638 SetToEndOf(parentNode);
640 template <typename ContainerType>
641 static SelfType After(
642 const ContainerType& aContainer,
643 InterlinePosition aInterlinePosition = InterlinePosition::Undefined) {
644 SelfType point;
645 point.SetAfter(&aContainer);
646 point.mInterlinePosition = aInterlinePosition;
647 return point;
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());
661 if (aPoint.mChild) {
662 return After(*aPoint.mChild, aInterlinePosition);
664 if (NS_WARN_IF(aPoint.IsEndOfContainer())) {
665 return SelfType();
667 auto point = aPoint.NextPoint().template To<SelfType>();
668 point.mInterlinePosition = aInterlinePosition;
669 return point;
673 * ParentPoint() returns a point whose child is the container.
675 template <typename EditorDOMPointType = SelfType>
676 EditorDOMPointType ParentPoint() const {
677 MOZ_ASSERT(mParent);
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
686 * the container.
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();
693 return result;
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();
701 return result;
705 * Clear() makes the instance not point anywhere.
707 void Clear() {
708 mParent = nullptr;
709 mChild = nullptr;
710 mOffset.reset();
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
720 * need to refer.
722 * @return true if there is a next DOM point to refer.
724 bool AdvanceOffset() {
725 if (NS_WARN_IF(!mParent)) {
726 return false;
728 // If only mOffset is available, just compute the offset.
729 if ((mOffset.isSome() && !mIsChildInitialized) ||
730 !mParent->IsContainerNode()) {
731 MOZ_ASSERT(mOffset.isSome());
732 MOZ_ASSERT(!mChild);
733 if (NS_WARN_IF(mOffset.value() >= mParent->Length())) {
734 // We're already referring the start of the container.
735 return false;
737 mOffset = mozilla::Some(mOffset.value() + 1);
738 mInterlinePosition = InterlinePosition::Undefined;
739 return true;
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).
747 return false;
750 if (mOffset.isSome()) {
751 MOZ_ASSERT(mOffset.isSome());
752 mOffset = mozilla::Some(mOffset.value() + 1);
754 mChild = mChild->GetNextSibling();
755 mInterlinePosition = InterlinePosition::Undefined;
756 return true;
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)) {
769 return false;
771 // If only mOffset is available, just compute the offset.
772 if ((mOffset.isSome() && !mIsChildInitialized) ||
773 !mParent->IsContainerNode()) {
774 MOZ_ASSERT(mOffset.isSome());
775 MOZ_ASSERT(!mChild);
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");
782 return false;
784 mOffset = mozilla::Some(mOffset.value() - 1);
785 mInterlinePosition = InterlinePosition::Undefined;
786 return true;
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?).
796 return false;
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.
803 return false;
806 if (mOffset.isSome()) {
807 mOffset = mozilla::Some(mOffset.value() - 1);
809 mChild = previousSibling;
810 mInterlinePosition = InterlinePosition::Undefined;
811 return true;
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>();
829 nsINode* parent;
830 for (parent = mParent->GetParentNode();
831 parent && parent->IsInNativeAnonymousSubtree();
832 parent = parent->GetParentNode()) {
834 if (!parent) {
835 return EditorDOMPointType();
837 return EditorDOMPointType(parent);
840 [[nodiscard]] bool IsSet() const {
841 return mParent && (mIsChildInitialized || mOffset.isSome());
844 [[nodiscard]] bool IsSetAndValid() const {
845 if (!IsSet()) {
846 return false;
849 if (mChild &&
850 (mChild->GetParentNode() != mParent || mChild->IsBeingRemoved())) {
851 return false;
853 if (mOffset.isSome() && mOffset.value() > mParent->Length()) {
854 return false;
856 return true;
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)) {
873 return false;
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");
882 return true;
884 NS_WARNING_ASSERTION(!mOffset.isSome() || mParent->GetChildAt_Deprecated(
885 mOffset.value()) == mChild,
886 "mOffset and mChild are mismatched");
887 return false;
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
899 // container.
900 if (NS_WARN_IF(!mParent)) {
901 return false;
903 if (!mParent->IsContainerNode()) {
904 return mOffset.value() == mParent->Length();
906 if (mIsChildInitialized) {
907 if (!mChild) {
908 NS_WARNING_ASSERTION(
909 !mOffset.isSome() || mOffset.value() == mParent->Length(),
910 "If mOffset was initialized, it should be length of the container");
911 return true;
913 NS_WARNING_ASSERTION(!mOffset.isSome() || mParent->GetChildAt_Deprecated(
914 mOffset.value()) == mChild,
915 "mOffset and mChild are mismatched");
916 return false;
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)) {
928 return false;
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 "
938 "container");
939 return true;
941 NS_WARNING_ASSERTION(!mOffset.isSome() || mParent->GetChildAt_Deprecated(
942 mOffset.value()) == mChild,
943 "mOffset and mChild are mismatched");
944 return false;
946 MOZ_ASSERT(mOffset.isSome());
947 return mOffset.value() == mParent->Length() - 1;
950 bool IsBRElementAtEndOfContainer() const {
951 if (NS_WARN_IF(!mParent)) {
952 return false;
954 if (!mParent->IsContainerNode()) {
955 return false;
957 const_cast<SelfType*>(this)->EnsureChild();
958 if (!mChild || mChild->GetNextSibling()) {
959 return false;
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()
1000 : nullptr);
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;
1006 return *this;
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;
1022 return result;
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) {
1032 return false;
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
1038 // changes.
1039 if (mOffset != aOther.mOffset) {
1040 return false;
1042 if (mChild == aOther.mChild) {
1043 return true;
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.
1049 return false;
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.
1055 return true;
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
1086 // members.
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
1113 // nullptr.
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.
1119 #ifdef DEBUG
1120 if (mChild) {
1121 MOZ_ASSERT(mParent == mChild->GetParentNode());
1122 MOZ_ASSERT(mParent->GetChildAt_Deprecated(mOffset.value()) == mChild);
1123 } else {
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
1130 // information.
1131 if (mOffset.isSome()) {
1132 return RawRangeBoundary(mParent, mOffset.value());
1134 if (mChild) {
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)) {
1144 return nullptr;
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()) {
1163 return false;
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()) {
1173 return false;
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 << " }";
1194 return aStream;
1197 private:
1198 void EnsureChild() {
1199 if (mIsChildInitialized) {
1200 return;
1202 if (!mParent) {
1203 MOZ_ASSERT(!mOffset.isSome());
1204 return;
1206 MOZ_ASSERT(mOffset.isSome());
1207 MOZ_ASSERT(mOffset.value() <= mParent->Length());
1208 mIsChildInitialized = true;
1209 if (!mParent->IsContainerNode()) {
1210 return;
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*,
1228 uint32_t);
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, \
1256 ...) \
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>;
1265 public:
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);
1345 void Clear() {
1346 mStart.Clear();
1347 mEnd.Clear();
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();
1412 parent =
1413 parent->GetClosestNativeAnonymousSubtreeRootParentOrHost()) {
1415 if (MOZ_UNLIKELY(!parent)) {
1416 return false;
1418 mStart.Set(parent);
1420 if (mEnd.IsInNativeAnonymousSubtree()) {
1421 nsIContent* parent = nullptr;
1422 for (parent = mEnd.template ContainerAs<nsIContent>()
1423 ->GetClosestNativeAnonymousSubtreeRootParentOrHost();
1424 parent && parent->IsInNativeAnonymousSubtree();
1425 parent =
1426 parent->GetClosestNativeAnonymousSubtreeRootParentOrHost()) {
1428 if (MOZ_UNLIKELY(!parent)) {
1429 return false;
1431 mEnd.SetAfter(parent);
1433 return true;
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)) {
1440 return nullptr;
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 << " }";
1449 } else {
1450 aStream << "{ mStart=" << aRange.mStart << ", mEnd=" << aRange.mEnd
1451 << " }";
1453 return aStream;
1456 private:
1457 EditorDOMPointType mStart;
1458 EditorDOMPointType mEnd;
1460 friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&,
1461 EditorDOMRange&, const char*,
1462 uint32_t);
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
1481 * node.
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 {
1489 public:
1490 AutoEditorDOMPointOffsetInvalidator() = delete;
1491 AutoEditorDOMPointOffsetInvalidator(
1492 const AutoEditorDOMPointOffsetInvalidator&) = delete;
1493 AutoEditorDOMPointOffsetInvalidator(AutoEditorDOMPointOffsetInvalidator&&) =
1494 delete;
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() {
1505 if (!mCanceled) {
1506 InvalidateOffset();
1511 * Manually, invalidate offset of the given point.
1513 void InvalidateOffset() {
1514 if (mChild) {
1515 mPoint.Set(mChild);
1516 } else {
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; }
1528 private:
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;
1535 bool mCanceled;
1538 class MOZ_STACK_CLASS AutoEditorDOMRangeOffsetsInvalidator final {
1539 public:
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();
1549 void Cancel() {
1550 mStartInvalidator.Cancel();
1551 mEndInvalidator.Cancel();
1554 private:
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
1562 * and offset in it.
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
1567 * they need.
1569 class MOZ_STACK_CLASS AutoEditorDOMPointChildInvalidator final {
1570 public:
1571 AutoEditorDOMPointChildInvalidator() = delete;
1572 AutoEditorDOMPointChildInvalidator(
1573 const AutoEditorDOMPointChildInvalidator&) = delete;
1574 AutoEditorDOMPointChildInvalidator(AutoEditorDOMPointChildInvalidator&&) =
1575 delete;
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() {
1585 if (!mCanceled) {
1586 InvalidateChild();
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; }
1600 private:
1601 EditorDOMPoint& mPoint;
1603 bool mCanceled;
1606 class MOZ_STACK_CLASS AutoEditorDOMRangeChildrenInvalidator final {
1607 public:
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();
1617 void Cancel() {
1618 mStartInvalidator.Cancel();
1619 mEndInvalidator.Cancel();
1622 private:
1623 AutoEditorDOMPointChildInvalidator mStartInvalidator;
1624 AutoEditorDOMPointChildInvalidator mEndInvalidator;
1627 } // namespace mozilla
1629 #endif // #ifndef mozilla_EditorDOMPoint_h