Bug 1772588 [wpt PR 34302] - [wpt] Add test for block-in-inline offsetParent., a...
[gecko.git] / editor / libeditor / EditorDOMPoint.h
blobe1cd969f2f2c2c70bf5258e5266ec8649c2819b4
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 typedef EditorDOMPointBase<ParentType, ChildType> SelfType;
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; }
202 nsIContent* GetContainerAsContent() const {
203 return nsIContent::FromNodeOrNull(mParent);
206 MOZ_NEVER_INLINE_DEBUG nsIContent* ContainerAsContent() const {
207 MOZ_ASSERT(mParent);
208 MOZ_ASSERT(mParent->IsContent());
209 return mParent->AsContent();
212 dom::Element* GetContainerAsElement() const {
213 return dom::Element::FromNodeOrNull(mParent);
216 MOZ_NEVER_INLINE_DEBUG dom::Element* ContainerAsElement() const {
217 MOZ_ASSERT(mParent);
218 MOZ_ASSERT(mParent->IsElement());
219 return mParent->AsElement();
222 nsStyledElement* GetContainerAsStyledElement() const {
223 return nsStyledElement::FromNodeOrNull(mParent);
226 dom::Text* GetContainerAsText() const {
227 return dom::Text::FromNodeOrNull(mParent);
230 MOZ_NEVER_INLINE_DEBUG dom::Text* ContainerAsText() const {
231 MOZ_ASSERT(mParent);
232 MOZ_ASSERT(IsInTextNode());
233 return mParent->AsText();
237 * GetContainerParent() returns parent of the container node at the point.
239 nsINode* GetContainerParent() const {
240 return mParent ? mParent->GetParent() : nullptr;
243 nsIContent* GetContainerParentAsContent() const {
244 return nsIContent::FromNodeOrNull(GetContainerParent());
247 dom::Element* GetContainerParentAsElement() const {
248 return dom::Element::FromNodeOrNull(GetContainerParent());
251 dom::Element* GetContainerOrContainerParentElement() const {
252 if (MOZ_UNLIKELY(!mParent)) {
253 return nullptr;
255 return mParent->IsElement() ? ContainerAsElement()
256 : GetContainerParentAsElement();
260 * CanContainerHaveChildren() returns true if the container node can have
261 * child nodes. Otherwise, e.g., when the container is a text node, returns
262 * false.
264 bool CanContainerHaveChildren() const {
265 return mParent && mParent->IsContainerNode();
269 * IsContainerEmpty() returns true if it has no children or its text is empty.
271 bool IsContainerEmpty() const { return mParent && !mParent->Length(); }
274 * IsInContentNode() returns true if the container is a subclass of
275 * nsIContent.
277 bool IsInContentNode() const { return mParent && mParent->IsContent(); }
280 * IsInDataNode() returns true if the container node is a data node including
281 * text node.
283 bool IsInDataNode() const { return mParent && mParent->IsCharacterData(); }
286 * IsInTextNode() returns true if the container node is a text node.
288 bool IsInTextNode() const { return mParent && mParent->IsText(); }
291 * IsInNativeAnonymousSubtree() returns true if the container is in
292 * native anonymous subtree.
294 bool IsInNativeAnonymousSubtree() const {
295 return mParent && mParent->IsInNativeAnonymousSubtree();
299 * IsContainerHTMLElement() returns true if the container node is an HTML
300 * element node and its node name is aTag.
302 bool IsContainerHTMLElement(nsAtom* aTag) const {
303 return mParent && mParent->IsHTMLElement(aTag);
307 * IsContainerAnyOfHTMLElements() returns true if the container node is an
308 * HTML element node and its node name is one of the arguments.
310 template <typename First, typename... Args>
311 bool IsContainerAnyOfHTMLElements(First aFirst, Args... aArgs) const {
312 return mParent && mParent->IsAnyOfHTMLElements(aFirst, aArgs...);
316 * GetChild() returns a child node which is pointed by the instance.
317 * If mChild hasn't been initialized yet, this computes the child node
318 * from mParent and mOffset with *current* DOM tree.
320 nsIContent* GetChild() const {
321 if (!mParent || !mParent->IsContainerNode()) {
322 return nullptr;
324 if (mIsChildInitialized) {
325 return mChild;
327 // Fix child node now.
328 const_cast<SelfType*>(this)->EnsureChild();
329 return mChild;
333 * GetCurrentChildAtOffset() returns current child at mOffset.
334 * I.e., mOffset needs to be fixed before calling this.
336 nsIContent* GetCurrentChildAtOffset() const {
337 MOZ_ASSERT(mOffset.isSome());
338 if (mOffset.isNothing()) {
339 return GetChild();
341 return mParent ? mParent->GetChildAt_Deprecated(*mOffset) : nullptr;
345 * GetChildOrContainerIfDataNode() returns the child content node,
346 * or container content node if the container is a data node.
348 nsIContent* GetChildOrContainerIfDataNode() const {
349 if (IsInDataNode()) {
350 return ContainerAsContent();
352 return GetChild();
356 * GetNextSiblingOfChild() returns next sibling of the child node.
357 * If this refers after the last child or the container cannot have children,
358 * this returns nullptr with warning.
359 * If mChild hasn't been initialized yet, this computes the child node
360 * from mParent and mOffset with *current* DOM tree.
362 nsIContent* GetNextSiblingOfChild() const {
363 if (NS_WARN_IF(!mParent) || NS_WARN_IF(!mParent->IsContainerNode())) {
364 return nullptr;
366 if (mIsChildInitialized) {
367 return mChild ? mChild->GetNextSibling() : nullptr;
369 MOZ_ASSERT(mOffset.isSome());
370 if (NS_WARN_IF(mOffset.value() > mParent->Length())) {
371 // If this has been set only offset and now the offset is invalid,
372 // let's just return nullptr.
373 return nullptr;
375 // Fix child node now.
376 const_cast<SelfType*>(this)->EnsureChild();
377 return mChild ? mChild->GetNextSibling() : nullptr;
381 * GetPreviousSiblingOfChild() returns previous sibling of a child
382 * at offset. If this refers the first child or the container cannot have
383 * children, this returns nullptr with warning.
384 * If mChild hasn't been initialized yet, this computes the child node
385 * from mParent and mOffset with *current* DOM tree.
387 nsIContent* GetPreviousSiblingOfChild() const {
388 if (NS_WARN_IF(!mParent) || NS_WARN_IF(!mParent->IsContainerNode())) {
389 return nullptr;
391 if (mIsChildInitialized) {
392 return mChild ? mChild->GetPreviousSibling() : mParent->GetLastChild();
394 MOZ_ASSERT(mOffset.isSome());
395 if (NS_WARN_IF(mOffset.value() > mParent->Length())) {
396 // If this has been set only offset and now the offset is invalid,
397 // let's just return nullptr.
398 return nullptr;
400 // Fix child node now.
401 const_cast<SelfType*>(this)->EnsureChild();
402 return mChild ? mChild->GetPreviousSibling() : mParent->GetLastChild();
406 * Simple accessors of the character in dom::Text so that when you call
407 * these methods, you need to guarantee that the container is a dom::Text.
409 MOZ_NEVER_INLINE_DEBUG char16_t Char() const {
410 MOZ_ASSERT(IsSetAndValid());
411 MOZ_ASSERT(!IsEndOfContainer());
412 return ContainerAsText()->TextFragment().CharAt(mOffset.value());
414 MOZ_NEVER_INLINE_DEBUG bool IsCharASCIISpace() const {
415 return nsCRT::IsAsciiSpace(Char());
417 MOZ_NEVER_INLINE_DEBUG bool IsCharNBSP() const { return Char() == 0x00A0; }
418 MOZ_NEVER_INLINE_DEBUG bool IsCharASCIISpaceOrNBSP() const {
419 char16_t ch = Char();
420 return nsCRT::IsAsciiSpace(ch) || ch == 0x00A0;
422 MOZ_NEVER_INLINE_DEBUG bool IsCharNewLine() const { return Char() == '\n'; }
423 MOZ_NEVER_INLINE_DEBUG bool IsCharPreformattedNewLine() const;
424 MOZ_NEVER_INLINE_DEBUG bool
425 IsCharPreformattedNewLineCollapsedWithWhiteSpaces() const;
427 * IsCharCollapsibleASCIISpace(), IsCharCollapsibleNBSP() and
428 * IsCharCollapsibleASCIISpaceOrNBSP() checks whether the white-space is
429 * preformatted or collapsible with the style of the container text node
430 * without flushing pending notifications.
432 bool IsCharCollapsibleASCIISpace() const;
433 bool IsCharCollapsibleNBSP() const;
434 bool IsCharCollapsibleASCIISpaceOrNBSP() const;
436 MOZ_NEVER_INLINE_DEBUG bool IsCharHighSurrogateFollowedByLowSurrogate()
437 const {
438 MOZ_ASSERT(IsSetAndValid());
439 MOZ_ASSERT(!IsEndOfContainer());
440 return ContainerAsText()
441 ->TextFragment()
442 .IsHighSurrogateFollowedByLowSurrogateAt(mOffset.value());
444 MOZ_NEVER_INLINE_DEBUG bool IsCharLowSurrogateFollowingHighSurrogate() const {
445 MOZ_ASSERT(IsSetAndValid());
446 MOZ_ASSERT(!IsEndOfContainer());
447 return ContainerAsText()
448 ->TextFragment()
449 .IsLowSurrogateFollowingHighSurrogateAt(mOffset.value());
452 MOZ_NEVER_INLINE_DEBUG char16_t PreviousChar() const {
453 MOZ_ASSERT(IsSetAndValid());
454 MOZ_ASSERT(!IsStartOfContainer());
455 return ContainerAsText()->TextFragment().CharAt(mOffset.value() - 1);
457 MOZ_NEVER_INLINE_DEBUG bool IsPreviousCharASCIISpace() const {
458 return nsCRT::IsAsciiSpace(PreviousChar());
460 MOZ_NEVER_INLINE_DEBUG bool IsPreviousCharNBSP() const {
461 return PreviousChar() == 0x00A0;
463 MOZ_NEVER_INLINE_DEBUG bool IsPreviousCharASCIISpaceOrNBSP() const {
464 char16_t ch = PreviousChar();
465 return nsCRT::IsAsciiSpace(ch) || ch == 0x00A0;
467 MOZ_NEVER_INLINE_DEBUG bool IsPreviousCharNewLine() const {
468 return PreviousChar() == '\n';
470 MOZ_NEVER_INLINE_DEBUG bool IsPreviousCharPreformattedNewLine() const;
471 MOZ_NEVER_INLINE_DEBUG bool
472 IsPreviousCharPreformattedNewLineCollapsedWithWhiteSpaces() const;
474 * IsPreviousCharCollapsibleASCIISpace(), IsPreviousCharCollapsibleNBSP() and
475 * IsPreviousCharCollapsibleASCIISpaceOrNBSP() checks whether the white-space
476 * is preformatted or collapsible with the style of the container text node
477 * without flushing pending notifications.
479 bool IsPreviousCharCollapsibleASCIISpace() const;
480 bool IsPreviousCharCollapsibleNBSP() const;
481 bool IsPreviousCharCollapsibleASCIISpaceOrNBSP() const;
483 MOZ_NEVER_INLINE_DEBUG char16_t NextChar() const {
484 MOZ_ASSERT(IsSetAndValid());
485 MOZ_ASSERT(!IsAtLastContent() && !IsEndOfContainer());
486 return ContainerAsText()->TextFragment().CharAt(mOffset.value() + 1);
488 MOZ_NEVER_INLINE_DEBUG bool IsNextCharASCIISpace() const {
489 return nsCRT::IsAsciiSpace(NextChar());
491 MOZ_NEVER_INLINE_DEBUG bool IsNextCharNBSP() const {
492 return NextChar() == 0x00A0;
494 MOZ_NEVER_INLINE_DEBUG bool IsNextCharASCIISpaceOrNBSP() const {
495 char16_t ch = NextChar();
496 return nsCRT::IsAsciiSpace(ch) || ch == 0x00A0;
498 MOZ_NEVER_INLINE_DEBUG bool IsNextCharNewLine() const {
499 return NextChar() == '\n';
501 MOZ_NEVER_INLINE_DEBUG bool IsNextCharPreformattedNewLine() const;
502 MOZ_NEVER_INLINE_DEBUG bool
503 IsNextCharPreformattedNewLineCollapsedWithWhiteSpaces() const;
505 * IsNextCharCollapsibleASCIISpace(), IsNextCharCollapsibleNBSP() and
506 * IsNextCharCollapsibleASCIISpaceOrNBSP() checks whether the white-space is
507 * preformatted or collapsible with the style of the container text node
508 * without flushing pending notifications.
510 bool IsNextCharCollapsibleASCIISpace() const;
511 bool IsNextCharCollapsibleNBSP() const;
512 bool IsNextCharCollapsibleASCIISpaceOrNBSP() const;
514 uint32_t Offset() const {
515 if (mOffset.isSome()) {
516 MOZ_ASSERT(mOffset.isSome());
517 return mOffset.value();
519 if (MOZ_UNLIKELY(!mParent)) {
520 MOZ_ASSERT(!mChild);
521 return 0u;
523 MOZ_ASSERT(mParent->IsContainerNode(),
524 "If the container cannot have children, mOffset.isSome() should "
525 "be true");
526 if (!mChild) {
527 // We're referring after the last child. Fix offset now.
528 const_cast<SelfType*>(this)->mOffset = mozilla::Some(mParent->Length());
529 return mOffset.value();
531 MOZ_ASSERT(mChild->GetParentNode() == mParent);
532 // Fix offset now.
533 if (mChild == mParent->GetFirstChild()) {
534 const_cast<SelfType*>(this)->mOffset = mozilla::Some(0u);
535 return 0u;
537 const_cast<SelfType*>(this)->mOffset = mParent->ComputeIndexOf(mChild);
538 MOZ_DIAGNOSTIC_ASSERT(mOffset.isSome());
539 return mOffset.valueOr(0u); // Avoid crash in Release/Beta
543 * Set() sets a point to aOffset or aChild.
544 * If it's set with aOffset, mChild is invalidated. If it's set with aChild,
545 * mOffset may be invalidated.
547 template <typename ContainerType>
548 void Set(ContainerType* aContainer, uint32_t aOffset) {
549 mParent = aContainer;
550 mChild = nullptr;
551 mOffset = mozilla::Some(aOffset);
552 mIsChildInitialized = false;
553 mInterlinePosition = InterlinePosition::Undefined;
554 NS_ASSERTION(!mParent || mOffset.value() <= mParent->Length(),
555 "The offset is out of bounds");
557 template <typename ContainerType, template <typename> typename StrongPtr>
558 void Set(const StrongPtr<ContainerType>& aContainer, uint32_t aOffset) {
559 Set(aContainer.get(), aOffset);
561 void Set(const nsINode* aChild) {
562 MOZ_ASSERT(aChild);
563 if (NS_WARN_IF(!aChild->IsContent())) {
564 Clear();
565 return;
567 mParent = aChild->GetParentNode();
568 mChild = const_cast<nsIContent*>(aChild->AsContent());
569 mOffset.reset();
570 mIsChildInitialized = true;
571 mInterlinePosition = InterlinePosition::Undefined;
575 * SetToEndOf() sets this to the end of aContainer. Then, mChild is always
576 * nullptr but marked as initialized and mOffset is always set.
578 template <typename ContainerType>
579 MOZ_NEVER_INLINE_DEBUG void SetToEndOf(const ContainerType* aContainer) {
580 MOZ_ASSERT(aContainer);
581 mParent = const_cast<ContainerType*>(aContainer);
582 mChild = nullptr;
583 mOffset = mozilla::Some(mParent->Length());
584 mIsChildInitialized = true;
585 mInterlinePosition = InterlinePosition::Undefined;
587 template <typename ContainerType, template <typename> typename StrongPtr>
588 MOZ_NEVER_INLINE_DEBUG void SetToEndOf(
589 const StrongPtr<ContainerType>& aContainer) {
590 SetToEndOf(aContainer.get());
592 template <typename ContainerType>
593 MOZ_NEVER_INLINE_DEBUG static SelfType AtEndOf(
594 const ContainerType& aContainer,
595 InterlinePosition aInterlinePosition = InterlinePosition::Undefined) {
596 SelfType point;
597 point.SetToEndOf(&aContainer);
598 point.mInterlinePosition = aInterlinePosition;
599 return point;
601 template <typename ContainerType, template <typename> typename StrongPtr>
602 MOZ_NEVER_INLINE_DEBUG static SelfType AtEndOf(
603 const StrongPtr<ContainerType>& aContainer,
604 InterlinePosition aInterlinePosition = InterlinePosition::Undefined) {
605 MOZ_ASSERT(aContainer.get());
606 return AtEndOf(*aContainer.get(), aInterlinePosition);
610 * SetAfter() sets mChild to next sibling of aChild.
612 void SetAfter(const nsINode* aChild) {
613 MOZ_ASSERT(aChild);
614 nsIContent* nextSibling = aChild->GetNextSibling();
615 if (nextSibling) {
616 Set(nextSibling);
617 return;
619 nsINode* parentNode = aChild->GetParentNode();
620 if (NS_WARN_IF(!parentNode)) {
621 Clear();
622 return;
624 SetToEndOf(parentNode);
626 template <typename ContainerType>
627 static SelfType After(
628 const ContainerType& aContainer,
629 InterlinePosition aInterlinePosition = InterlinePosition::Undefined) {
630 SelfType point;
631 point.SetAfter(&aContainer);
632 point.mInterlinePosition = aInterlinePosition;
633 return point;
635 template <typename ContainerType, template <typename> typename StrongPtr>
636 MOZ_NEVER_INLINE_DEBUG static SelfType After(
637 const StrongPtr<ContainerType>& aContainer,
638 InterlinePosition aInterlinePosition = InterlinePosition::Undefined) {
639 MOZ_ASSERT(aContainer.get());
640 return After(*aContainer.get(), aInterlinePosition);
642 template <typename PT, typename CT>
643 MOZ_NEVER_INLINE_DEBUG static SelfType After(
644 const EditorDOMPointBase<PT, CT>& aPoint,
645 InterlinePosition aInterlinePosition = InterlinePosition::Undefined) {
646 MOZ_ASSERT(aPoint.IsSet());
647 if (aPoint.mChild) {
648 return After(*aPoint.mChild, aInterlinePosition);
650 if (NS_WARN_IF(aPoint.IsEndOfContainer())) {
651 return SelfType();
653 auto point = aPoint.NextPoint().template To<SelfType>();
654 point.mInterlinePosition = aInterlinePosition;
655 return point;
659 * ParentPoint() returns a point whose child is the container.
661 template <typename EditorDOMPointType = SelfType>
662 EditorDOMPointType ParentPoint() const {
663 MOZ_ASSERT(mParent);
664 if (MOZ_UNLIKELY(!mParent) || !mParent->IsContent()) {
665 return EditorDOMPointType();
667 return EditorDOMPointType(ContainerAsContent());
671 * NextPoint() and PreviousPoint() returns next/previous DOM point in
672 * the container.
674 template <typename EditorDOMPointType = SelfType>
675 EditorDOMPointType NextPoint() const {
676 NS_ASSERTION(!IsEndOfContainer(), "Should not be at end of the container");
677 auto result = this->template To<EditorDOMPointType>();
678 result.AdvanceOffset();
679 return result;
681 template <typename EditorDOMPointType = SelfType>
682 EditorDOMPointType PreviousPoint() const {
683 NS_ASSERTION(!IsStartOfContainer(),
684 "Should not be at start of the container");
685 EditorDOMPointType result = this->template To<EditorDOMPointType>();
686 result.RewindOffset();
687 return result;
691 * Clear() makes the instance not point anywhere.
693 void Clear() {
694 mParent = nullptr;
695 mChild = nullptr;
696 mOffset.reset();
697 mIsChildInitialized = false;
698 mInterlinePosition = InterlinePosition::Undefined;
702 * AdvanceOffset() tries to refer next sibling of mChild and/of next offset.
703 * If the container can have children and there is no next sibling or the
704 * offset reached the length of the container, this outputs warning and does
705 * nothing. So, callers need to check if there is next sibling which you
706 * need to refer.
708 * @return true if there is a next DOM point to refer.
710 bool AdvanceOffset() {
711 if (NS_WARN_IF(!mParent)) {
712 return false;
714 // If only mOffset is available, just compute the offset.
715 if ((mOffset.isSome() && !mIsChildInitialized) ||
716 !mParent->IsContainerNode()) {
717 MOZ_ASSERT(mOffset.isSome());
718 MOZ_ASSERT(!mChild);
719 if (NS_WARN_IF(mOffset.value() >= mParent->Length())) {
720 // We're already referring the start of the container.
721 return false;
723 mOffset = mozilla::Some(mOffset.value() + 1);
724 mInterlinePosition = InterlinePosition::Undefined;
725 return true;
728 MOZ_ASSERT(mIsChildInitialized);
729 MOZ_ASSERT(!mOffset.isSome() || mOffset.isSome());
730 if (NS_WARN_IF(!mParent->HasChildren()) || NS_WARN_IF(!mChild) ||
731 NS_WARN_IF(mOffset.isSome() && mOffset.value() >= mParent->Length())) {
732 // We're already referring the end of the container (or outside).
733 return false;
736 if (mOffset.isSome()) {
737 MOZ_ASSERT(mOffset.isSome());
738 mOffset = mozilla::Some(mOffset.value() + 1);
740 mChild = mChild->GetNextSibling();
741 mInterlinePosition = InterlinePosition::Undefined;
742 return true;
746 * RewindOffset() tries to refer previous sibling of mChild and/or previous
747 * offset. If the container can have children and there is no next previous
748 * or the offset is 0, this outputs warning and does nothing. So, callers
749 * need to check if there is previous sibling which you need to refer.
751 * @return true if there is a previous DOM point to refer.
753 bool RewindOffset() {
754 if (NS_WARN_IF(!mParent)) {
755 return false;
757 // If only mOffset is available, just compute the offset.
758 if ((mOffset.isSome() && !mIsChildInitialized) ||
759 !mParent->IsContainerNode()) {
760 MOZ_ASSERT(mOffset.isSome());
761 MOZ_ASSERT(!mChild);
762 if (NS_WARN_IF(!mOffset.value()) ||
763 NS_WARN_IF(mOffset.value() > mParent->Length())) {
764 // We're already referring the start of the container or
765 // the offset is invalid since perhaps, the offset was set before
766 // the last DOM tree change.
767 NS_ASSERTION(false, "Failed to rewind offset");
768 return false;
770 mOffset = mozilla::Some(mOffset.value() - 1);
771 mInterlinePosition = InterlinePosition::Undefined;
772 return true;
775 MOZ_ASSERT(mIsChildInitialized);
776 MOZ_ASSERT(!mOffset.isSome() || mOffset.isSome());
777 if (NS_WARN_IF(!mParent->HasChildren()) ||
778 NS_WARN_IF(mChild && !mChild->GetPreviousSibling()) ||
779 NS_WARN_IF(mOffset.isSome() && !mOffset.value())) {
780 // We're already referring the start of the container (or the child has
781 // been moved from the container?).
782 return false;
785 nsIContent* previousSibling =
786 mChild ? mChild->GetPreviousSibling() : mParent->GetLastChild();
787 if (NS_WARN_IF(!previousSibling)) {
788 // We're already referring the first child of the container.
789 return false;
792 if (mOffset.isSome()) {
793 mOffset = mozilla::Some(mOffset.value() - 1);
795 mChild = previousSibling;
796 mInterlinePosition = InterlinePosition::Undefined;
797 return true;
801 * GetNonAnonymousSubtreePoint() returns a DOM point which is NOT in
802 * native-anonymous subtree. If the instance isn't in native-anonymous
803 * subtree, this returns same point. Otherwise, climbs up until finding
804 * non-native-anonymous parent and returns the point of it. I.e.,
805 * container is parent of the found non-anonymous-native node.
807 template <typename EditorDOMPointType>
808 EditorDOMPointType GetNonAnonymousSubtreePoint() const {
809 if (NS_WARN_IF(!IsSet())) {
810 return EditorDOMPointType();
812 if (!IsInNativeAnonymousSubtree()) {
813 return this->template To<EditorDOMPointType>();
815 nsINode* parent;
816 for (parent = mParent->GetParentNode();
817 parent && parent->IsInNativeAnonymousSubtree();
818 parent = parent->GetParentNode()) {
820 if (!parent) {
821 return EditorDOMPointType();
823 return EditorDOMPointType(parent);
826 bool IsSet() const {
827 return mParent && (mIsChildInitialized || mOffset.isSome());
830 bool IsSetAndValid() const {
831 if (!IsSet()) {
832 return false;
835 if (mChild &&
836 (mChild->GetParentNode() != mParent || mChild->IsBeingRemoved())) {
837 return false;
839 if (mOffset.isSome() && mOffset.value() > mParent->Length()) {
840 return false;
842 return true;
845 bool HasChildMovedFromContainer() const {
846 return mChild && mChild->GetParentNode() != mParent;
849 bool IsStartOfContainer() const {
850 // If we're referring the first point in the container:
851 // If mParent is not a container like a text node, mOffset is 0.
852 // If mChild is initialized and it's first child of mParent.
853 // If mChild isn't initialized and the offset is 0.
854 if (NS_WARN_IF(!mParent)) {
855 return false;
857 if (!mParent->IsContainerNode()) {
858 return !mOffset.value();
860 if (mIsChildInitialized) {
861 if (mParent->GetFirstChild() == mChild) {
862 NS_WARNING_ASSERTION(!mOffset.isSome() || !mOffset.value(),
863 "If mOffset was initialized, it should be 0");
864 return true;
866 NS_WARNING_ASSERTION(!mOffset.isSome() || mParent->GetChildAt_Deprecated(
867 mOffset.value()) == mChild,
868 "mOffset and mChild are mismatched");
869 return false;
871 MOZ_ASSERT(mOffset.isSome());
872 return !mOffset.value();
875 bool IsEndOfContainer() const {
876 // If we're referring after the last point of the container:
877 // If mParent is not a container like text node, mOffset is same as the
878 // length of the container.
879 // If mChild is initialized and it's nullptr.
880 // If mChild isn't initialized and mOffset is same as the length of the
881 // container.
882 if (NS_WARN_IF(!mParent)) {
883 return false;
885 if (!mParent->IsContainerNode()) {
886 return mOffset.value() == mParent->Length();
888 if (mIsChildInitialized) {
889 if (!mChild) {
890 NS_WARNING_ASSERTION(
891 !mOffset.isSome() || mOffset.value() == mParent->Length(),
892 "If mOffset was initialized, it should be length of the container");
893 return true;
895 NS_WARNING_ASSERTION(!mOffset.isSome() || mParent->GetChildAt_Deprecated(
896 mOffset.value()) == mChild,
897 "mOffset and mChild are mismatched");
898 return false;
900 MOZ_ASSERT(mOffset.isSome());
901 return mOffset.value() == mParent->Length();
905 * IsAtLastContent() returns true when it refers last child of the container
906 * or last character offset of text node.
908 bool IsAtLastContent() const {
909 if (NS_WARN_IF(!mParent)) {
910 return false;
912 if (mParent->IsContainerNode() && mOffset.isSome()) {
913 return mOffset.value() == mParent->Length() - 1;
915 if (mIsChildInitialized) {
916 if (mChild && mChild == mParent->GetLastChild()) {
917 NS_WARNING_ASSERTION(
918 !mOffset.isSome() || mOffset.value() == mParent->Length() - 1,
919 "If mOffset was initialized, it should be length - 1 of the "
920 "container");
921 return true;
923 NS_WARNING_ASSERTION(!mOffset.isSome() || mParent->GetChildAt_Deprecated(
924 mOffset.value()) == mChild,
925 "mOffset and mChild are mismatched");
926 return false;
928 MOZ_ASSERT(mOffset.isSome());
929 return mOffset.value() == mParent->Length() - 1;
932 bool IsBRElementAtEndOfContainer() const {
933 if (NS_WARN_IF(!mParent)) {
934 return false;
936 if (!mParent->IsContainerNode()) {
937 return false;
939 const_cast<SelfType*>(this)->EnsureChild();
940 if (!mChild || mChild->GetNextSibling()) {
941 return false;
943 return mChild->IsHTMLElement(nsGkAtoms::br);
946 template <typename A, typename B>
947 EditorDOMPointBase& operator=(const RangeBoundaryBase<A, B>& aOther) {
948 mParent = aOther.mParent;
949 mChild = aOther.mRef ? aOther.mRef->GetNextSibling()
950 : (aOther.mParent && aOther.mParent->IsContainerNode()
951 ? aOther.mParent->GetFirstChild()
952 : nullptr);
953 mOffset = aOther.mOffset;
954 mIsChildInitialized =
955 aOther.mRef || (aOther.mParent && !aOther.mParent->IsContainerNode()) ||
956 (aOther.mOffset.isSome() && !aOther.mOffset.value());
957 mInterlinePosition = InterlinePosition::Undefined;
958 return *this;
961 template <typename EditorDOMPointType>
962 constexpr EditorDOMPointType To() const {
963 // XXX Cannot specialize this method due to implicit instantiatation caused
964 // by the inline CC functions below.
965 if (std::is_same<SelfType, EditorDOMPointType>::value) {
966 return reinterpret_cast<const EditorDOMPointType&>(*this);
968 EditorDOMPointType result;
969 result.mParent = mParent;
970 result.mChild = mChild;
971 result.mOffset = mOffset;
972 result.mIsChildInitialized = mIsChildInitialized;
973 result.mInterlinePosition = mInterlinePosition;
974 return result;
978 * Don't compare mInterlinePosition. If it's required to check, perhaps,
979 * another compare operator like `===` should be created.
981 template <typename A, typename B>
982 bool operator==(const EditorDOMPointBase<A, B>& aOther) const {
983 if (mParent != aOther.mParent) {
984 return false;
987 if (mOffset.isSome() && aOther.mOffset.isSome()) {
988 // If both mOffset are set, we need to compare both mRef too because
989 // the relation of mRef and mOffset have already broken by DOM tree
990 // changes.
991 if (mOffset != aOther.mOffset) {
992 return false;
994 if (mChild == aOther.mChild) {
995 return true;
997 if (NS_WARN_IF(mIsChildInitialized && aOther.mIsChildInitialized)) {
998 // In this case, relation between mChild and mOffset of one of or both
999 // of them doesn't match with current DOM tree since the DOM tree might
1000 // have been changed after computing mChild or mOffset.
1001 return false;
1003 // If one of mChild hasn't been computed yet, we should compare them only
1004 // with mOffset. Perhaps, we shouldn't copy mChild from non-nullptr one
1005 // to the other since if we copy it here, it may be unexpected behavior
1006 // for some callers.
1007 return true;
1010 MOZ_ASSERT(mIsChildInitialized || aOther.mIsChildInitialized);
1012 if (mOffset.isSome() && !mIsChildInitialized && !aOther.mOffset.isSome() &&
1013 aOther.mIsChildInitialized) {
1014 // If this has only mOffset and the other has only mChild, this needs to
1015 // compute mChild now.
1016 const_cast<SelfType*>(this)->EnsureChild();
1017 return mChild == aOther.mChild;
1020 if (!mOffset.isSome() && mIsChildInitialized && aOther.mOffset.isSome() &&
1021 !aOther.mIsChildInitialized) {
1022 // If this has only mChild and the other has only mOffset, the other needs
1023 // to compute mChild now.
1024 const_cast<EditorDOMPointBase<A, B>&>(aOther).EnsureChild();
1025 return mChild == aOther.mChild;
1028 // If mOffset of one of them hasn't been computed from mChild yet, we should
1029 // compare only with mChild. Perhaps, we shouldn't copy mOffset from being
1030 // some one to not being some one since if we copy it here, it may be
1031 // unexpected behavior for some callers.
1032 return mChild == aOther.mChild;
1035 template <typename A, typename B>
1036 bool operator==(const RangeBoundaryBase<A, B>& aOther) const {
1037 // TODO: Optimize this with directly comparing with RangeBoundaryBase
1038 // members.
1039 return *this == SelfType(aOther);
1042 template <typename A, typename B>
1043 bool operator!=(const EditorDOMPointBase<A, B>& aOther) const {
1044 return !(*this == aOther);
1047 template <typename A, typename B>
1048 bool operator!=(const RangeBoundaryBase<A, B>& aOther) const {
1049 return !(*this == aOther);
1053 * This operator should be used if API of other modules take RawRangeBoundary,
1054 * e.g., methods of Selection and nsRange.
1056 operator const RawRangeBoundary() const { return ToRawRangeBoundary(); }
1057 const RawRangeBoundary ToRawRangeBoundary() const {
1058 if (!IsSet() || NS_WARN_IF(!mIsChildInitialized && !mOffset.isSome())) {
1059 return RawRangeBoundary();
1061 if (!mParent->IsContainerNode()) {
1062 MOZ_ASSERT(mOffset.value() <= mParent->Length());
1063 // If the container is a data node like a text node, we need to create
1064 // RangeBoundaryBase instance only with mOffset because mChild is always
1065 // nullptr.
1066 return RawRangeBoundary(mParent, mOffset.value());
1068 if (mIsChildInitialized && mOffset.isSome()) {
1069 // If we've already set both child and offset, we should create
1070 // RangeBoundary with offset after validation.
1071 #ifdef DEBUG
1072 if (mChild) {
1073 MOZ_ASSERT(mParent == mChild->GetParentNode());
1074 MOZ_ASSERT(mParent->GetChildAt_Deprecated(mOffset.value()) == mChild);
1075 } else {
1076 MOZ_ASSERT(mParent->Length() == mOffset.value());
1078 #endif // #ifdef DEBUG
1079 return RawRangeBoundary(mParent, mOffset.value());
1081 // Otherwise, we should create RangeBoundaryBase only with available
1082 // information.
1083 if (mOffset.isSome()) {
1084 return RawRangeBoundary(mParent, mOffset.value());
1086 if (mChild) {
1087 return RawRangeBoundary(mParent, mChild->GetPreviousSibling());
1089 return RawRangeBoundary(mParent, mParent->GetLastChild());
1092 EditorDOMPointInText GetAsInText() const {
1093 return IsInTextNode() ? EditorDOMPointInText(ContainerAsText(), Offset(),
1094 mInterlinePosition)
1095 : EditorDOMPointInText();
1097 MOZ_NEVER_INLINE_DEBUG EditorDOMPointInText AsInText() const {
1098 MOZ_ASSERT(IsInTextNode());
1099 return EditorDOMPointInText(ContainerAsText(), Offset(),
1100 mInterlinePosition);
1103 template <typename A, typename B>
1104 bool IsBefore(const EditorDOMPointBase<A, B>& aOther) const {
1105 if (!IsSetAndValid() || !aOther.IsSetAndValid()) {
1106 return false;
1108 Maybe<int32_t> comp = nsContentUtils::ComparePoints(
1109 ToRawRangeBoundary(), aOther.ToRawRangeBoundary());
1110 return comp.isSome() && comp.value() == -1;
1113 template <typename A, typename B>
1114 bool EqualsOrIsBefore(const EditorDOMPointBase<A, B>& aOther) const {
1115 if (!IsSetAndValid() || !aOther.IsSetAndValid()) {
1116 return false;
1118 Maybe<int32_t> comp = nsContentUtils::ComparePoints(
1119 ToRawRangeBoundary(), aOther.ToRawRangeBoundary());
1120 return comp.isSome() && comp.value() <= 0;
1123 friend std::ostream& operator<<(std::ostream& aStream,
1124 const SelfType& aDOMPoint) {
1125 aStream << "{ mParent=" << aDOMPoint.mParent.get();
1126 if (aDOMPoint.mParent) {
1127 aStream << " (" << *aDOMPoint.mParent
1128 << ", Length()=" << aDOMPoint.mParent->Length() << ")";
1130 aStream << ", mChild=" << aDOMPoint.mChild.get();
1131 if (aDOMPoint.mChild) {
1132 aStream << " (" << *aDOMPoint.mChild << ")";
1134 aStream << ", mOffset=" << aDOMPoint.mOffset << ", mIsChildInitialized="
1135 << (aDOMPoint.mIsChildInitialized ? "true" : "false")
1136 << ", mInterlinePosition=" << aDOMPoint.mInterlinePosition << " }";
1137 return aStream;
1140 private:
1141 void EnsureChild() {
1142 if (mIsChildInitialized) {
1143 return;
1145 if (!mParent) {
1146 MOZ_ASSERT(!mOffset.isSome());
1147 return;
1149 MOZ_ASSERT(mOffset.isSome());
1150 MOZ_ASSERT(mOffset.value() <= mParent->Length());
1151 mIsChildInitialized = true;
1152 if (!mParent->IsContainerNode()) {
1153 return;
1155 mChild = mParent->GetChildAt_Deprecated(mOffset.value());
1156 MOZ_ASSERT(mChild || mOffset.value() == mParent->Length());
1159 ParentType mParent = nullptr;
1160 ChildType mChild = nullptr;
1162 Maybe<uint32_t> mOffset;
1163 InterlinePosition mInterlinePosition = InterlinePosition::Undefined;
1164 bool mIsChildInitialized = false;
1166 template <typename PT, typename CT>
1167 friend class EditorDOMPointBase;
1169 friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&,
1170 EditorDOMPoint&, const char*,
1171 uint32_t);
1172 friend void ImplCycleCollectionUnlink(EditorDOMPoint&);
1175 inline void ImplCycleCollectionUnlink(EditorDOMPoint& aField) {
1176 ImplCycleCollectionUnlink(aField.mParent);
1177 ImplCycleCollectionUnlink(aField.mChild);
1180 inline void ImplCycleCollectionTraverse(
1181 nsCycleCollectionTraversalCallback& aCallback, EditorDOMPoint& aField,
1182 const char* aName, uint32_t aFlags) {
1183 ImplCycleCollectionTraverse(aCallback, aField.mParent, "mParent", 0);
1184 ImplCycleCollectionTraverse(aCallback, aField.mChild, "mChild", 0);
1188 * EditorDOMRangeBase class stores a pair of same EditorDOMPointBase type.
1189 * The instance must be created with valid DOM points and start must be
1190 * before or same as end.
1192 template <typename EditorDOMPointType>
1193 class EditorDOMRangeBase final {
1194 public:
1195 using PointType = EditorDOMPointType;
1197 EditorDOMRangeBase() = default;
1198 template <typename PT, typename CT>
1199 explicit EditorDOMRangeBase(const EditorDOMPointBase<PT, CT>& aStart)
1200 : mStart(aStart), mEnd(aStart) {
1201 MOZ_ASSERT(!mStart.IsSet() || mStart.IsSetAndValid());
1203 template <typename StartPointType, typename EndPointType>
1204 explicit EditorDOMRangeBase(const StartPointType& aStart,
1205 const EndPointType& aEnd)
1206 : mStart(aStart.template To<PointType>()),
1207 mEnd(aEnd.template To<PointType>()) {
1208 MOZ_ASSERT_IF(mStart.IsSet(), mStart.IsSetAndValid());
1209 MOZ_ASSERT_IF(mEnd.IsSet(), mEnd.IsSetAndValid());
1210 MOZ_ASSERT_IF(mStart.IsSet() && mEnd.IsSet(),
1211 mStart.EqualsOrIsBefore(mEnd));
1213 explicit EditorDOMRangeBase(const dom::AbstractRange& aRange)
1214 : mStart(aRange.StartRef()), mEnd(aRange.EndRef()) {
1215 MOZ_ASSERT_IF(mStart.IsSet(), mStart.IsSetAndValid());
1216 MOZ_ASSERT_IF(mEnd.IsSet(), mEnd.IsSetAndValid());
1217 MOZ_ASSERT_IF(mStart.IsSet() && mEnd.IsSet(),
1218 mStart.EqualsOrIsBefore(mEnd));
1221 template <typename MaybeOtherPointType>
1222 MOZ_NEVER_INLINE_DEBUG void SetStart(const MaybeOtherPointType& aStart) {
1223 mStart = aStart.template To<PointType>();
1225 template <typename MaybeOtherPointType>
1226 MOZ_NEVER_INLINE_DEBUG void SetEnd(const MaybeOtherPointType& aEnd) {
1227 mEnd = aEnd.template To<PointType>();
1229 template <typename StartPointType, typename EndPointType>
1230 MOZ_NEVER_INLINE_DEBUG void SetStartAndEnd(const StartPointType& aStart,
1231 const EndPointType& aEnd) {
1232 MOZ_ASSERT_IF(aStart.IsSet() && aEnd.IsSet(),
1233 aStart.EqualsOrIsBefore(aEnd));
1234 mStart = aStart.template To<PointType>();
1235 mEnd = aEnd.template To<PointType>();
1237 void Clear() {
1238 mStart.Clear();
1239 mEnd.Clear();
1242 const PointType& StartRef() const { return mStart; }
1243 const PointType& EndRef() const { return mEnd; }
1245 bool Collapsed() const {
1246 MOZ_ASSERT(IsPositioned());
1247 return mStart == mEnd;
1249 bool IsPositioned() const { return mStart.IsSet() && mEnd.IsSet(); }
1250 bool IsPositionedAndValid() const {
1251 return mStart.IsSetAndValid() && mEnd.IsSetAndValid() &&
1252 mStart.EqualsOrIsBefore(mEnd);
1254 template <typename OtherPointType>
1255 MOZ_NEVER_INLINE_DEBUG bool Contains(const OtherPointType& aPoint) const {
1256 MOZ_ASSERT(aPoint.IsSetAndValid());
1257 return IsPositioned() && aPoint.IsSet() &&
1258 mStart.EqualsOrIsBefore(aPoint) && aPoint.IsBefore(mEnd);
1260 bool InSameContainer() const {
1261 MOZ_ASSERT(IsPositioned());
1262 return IsPositioned() && mStart.GetContainer() == mEnd.GetContainer();
1264 bool IsInContentNodes() const {
1265 MOZ_ASSERT(IsPositioned());
1266 return IsPositioned() && mStart.IsInContentNode() && mEnd.IsInContentNode();
1268 bool IsInTextNodes() const {
1269 MOZ_ASSERT(IsPositioned());
1270 return IsPositioned() && mStart.IsInTextNode() && mEnd.IsInTextNode();
1272 template <typename OtherRangeType>
1273 bool operator==(const OtherRangeType& aOther) const {
1274 return (!IsPositioned() && !aOther.IsPositioned()) ||
1275 (mStart == aOther.mStart && mEnd == aOther.mEnd);
1277 template <typename OtherRangeType>
1278 bool operator!=(const OtherRangeType& aOther) const {
1279 return !(*this == aOther);
1282 EditorDOMRangeInTexts GetAsInTexts() const {
1283 return IsInTextNodes()
1284 ? EditorDOMRangeInTexts(mStart.AsInText(), mEnd.AsInText())
1285 : EditorDOMRangeInTexts();
1287 MOZ_NEVER_INLINE_DEBUG EditorDOMRangeInTexts AsInTexts() const {
1288 MOZ_ASSERT(IsInTextNodes());
1289 return EditorDOMRangeInTexts(mStart.AsInText(), mEnd.AsInText());
1292 bool EnsureNotInNativeAnonymousSubtree() {
1293 if (mStart.IsInNativeAnonymousSubtree()) {
1294 nsIContent* parent = nullptr;
1295 for (parent = mStart.ContainerAsContent()
1296 ->GetClosestNativeAnonymousSubtreeRootParent();
1297 parent && parent->IsInNativeAnonymousSubtree();
1298 parent = parent->GetClosestNativeAnonymousSubtreeRootParent()) {
1300 if (MOZ_UNLIKELY(!parent)) {
1301 return false;
1303 mStart.Set(parent);
1305 if (mEnd.IsInNativeAnonymousSubtree()) {
1306 nsIContent* parent = nullptr;
1307 for (parent = mEnd.ContainerAsContent()
1308 ->GetClosestNativeAnonymousSubtreeRootParent();
1309 parent && parent->IsInNativeAnonymousSubtree();
1310 parent = parent->GetClosestNativeAnonymousSubtreeRootParent()) {
1312 if (MOZ_UNLIKELY(!parent)) {
1313 return false;
1315 mEnd.SetAfter(parent);
1317 return true;
1320 private:
1321 EditorDOMPointType mStart;
1322 EditorDOMPointType mEnd;
1324 friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&,
1325 EditorDOMRange&, const char*,
1326 uint32_t);
1327 friend void ImplCycleCollectionUnlink(EditorDOMRange&);
1330 inline void ImplCycleCollectionUnlink(EditorDOMRange& aField) {
1331 ImplCycleCollectionUnlink(aField.mStart);
1332 ImplCycleCollectionUnlink(aField.mEnd);
1335 inline void ImplCycleCollectionTraverse(
1336 nsCycleCollectionTraversalCallback& aCallback, EditorDOMRange& aField,
1337 const char* aName, uint32_t aFlags) {
1338 ImplCycleCollectionTraverse(aCallback, aField.mStart, "mStart", 0);
1339 ImplCycleCollectionTraverse(aCallback, aField.mEnd, "mEnd", 0);
1343 * AutoEditorDOMPointOffsetInvalidator is useful if DOM tree will be changed
1344 * when EditorDOMPoint instance is available and keeps referring same child
1345 * node.
1347 * This class automatically guarantees that given EditorDOMPoint instance
1348 * stores the child node and invalidates its offset when the instance is
1349 * destroyed. Additionally, users of this class can invalidate the offset
1350 * manually when they need.
1352 class MOZ_STACK_CLASS AutoEditorDOMPointOffsetInvalidator final {
1353 public:
1354 explicit AutoEditorDOMPointOffsetInvalidator(EditorDOMPoint& aPoint)
1355 : mPoint(aPoint), mCanceled(false) {
1356 MOZ_ASSERT(aPoint.IsSetAndValid());
1357 MOZ_ASSERT(mPoint.CanContainerHaveChildren());
1358 mChild = mPoint.GetChild();
1361 ~AutoEditorDOMPointOffsetInvalidator() {
1362 if (!mCanceled) {
1363 InvalidateOffset();
1368 * Manually, invalidate offset of the given point.
1370 void InvalidateOffset() {
1371 if (mChild) {
1372 mPoint.Set(mChild);
1373 } else {
1374 // If the point referred after the last child, let's keep referring
1375 // after current last node of the old container.
1376 mPoint.SetToEndOf(mPoint.GetContainer());
1381 * After calling Cancel(), mPoint won't be modified by the destructor.
1383 void Cancel() { mCanceled = true; }
1385 private:
1386 EditorDOMPoint& mPoint;
1387 // Needs to store child node by ourselves because EditorDOMPoint stores
1388 // child node with mRef which is previous sibling of current child node.
1389 // Therefore, we cannot keep referring it if it's first child.
1390 nsCOMPtr<nsIContent> mChild;
1392 bool mCanceled;
1394 AutoEditorDOMPointOffsetInvalidator() = delete;
1395 AutoEditorDOMPointOffsetInvalidator(
1396 const AutoEditorDOMPointOffsetInvalidator& aOther) = delete;
1397 const AutoEditorDOMPointOffsetInvalidator& operator=(
1398 const AutoEditorDOMPointOffsetInvalidator& aOther) = delete;
1402 * AutoEditorDOMPointChildInvalidator is useful if DOM tree will be changed
1403 * when EditorDOMPoint instance is available and keeps referring same container
1404 * and offset in it.
1406 * This class automatically guarantees that given EditorDOMPoint instance
1407 * stores offset and invalidates its child node when the instance is destroyed.
1408 * Additionally, users of this class can invalidate the child manually when
1409 * they need.
1411 class MOZ_STACK_CLASS AutoEditorDOMPointChildInvalidator final {
1412 public:
1413 explicit AutoEditorDOMPointChildInvalidator(EditorDOMPoint& aPoint)
1414 : mPoint(aPoint), mCanceled(false) {
1415 MOZ_ASSERT(aPoint.IsSetAndValid());
1416 Unused << mPoint.Offset();
1419 ~AutoEditorDOMPointChildInvalidator() {
1420 if (!mCanceled) {
1421 InvalidateChild();
1426 * Manually, invalidate child of the given point.
1428 void InvalidateChild() { mPoint.Set(mPoint.GetContainer(), mPoint.Offset()); }
1431 * After calling Cancel(), mPoint won't be modified by the destructor.
1433 void Cancel() { mCanceled = true; }
1435 private:
1436 EditorDOMPoint& mPoint;
1438 bool mCanceled;
1440 AutoEditorDOMPointChildInvalidator() = delete;
1441 AutoEditorDOMPointChildInvalidator(
1442 const AutoEditorDOMPointChildInvalidator& aOther) = delete;
1443 const AutoEditorDOMPointChildInvalidator& operator=(
1444 const AutoEditorDOMPointChildInvalidator& aOther) = delete;
1447 } // namespace mozilla
1449 #endif // #ifndef mozilla_EditorDOMPoint_h