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