EditorDOMPoint.h (73944B)
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/. */ 5 6 #ifndef mozilla_EditorDOMPoint_h 7 #define mozilla_EditorDOMPoint_h 8 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/dom/AbstractRange.h" 15 #include "mozilla/dom/Element.h" 16 #include "mozilla/dom/Selection.h" // for Selection::InterlinePosition 17 #include "mozilla/dom/Text.h" 18 #include "nsAtom.h" 19 #include "nsCOMPtr.h" 20 #include "nsContentUtils.h" 21 #include "nsCRT.h" 22 #include "nsGkAtoms.h" 23 #include "nsIContent.h" 24 #include "nsINode.h" 25 #include "nsString.h" 26 #include "nsStyledElement.h" 27 28 #include <algorithm> 29 #include <type_traits> 30 31 namespace mozilla { 32 33 /** 34 * EditorDOMPoint and EditorRawDOMPoint are simple classes which refers 35 * a point in the DOM tree at creating the instance or initializing the 36 * instance with calling Set(). 37 * 38 * EditorDOMPoint refers container node (and child node if it's already set) 39 * with nsCOMPtr. EditorRawDOMPoint refers them with raw pointer. 40 * So, EditorRawDOMPoint is useful when you access the nodes only before 41 * changing DOM tree since increasing refcount may appear in micro benchmark 42 * if it's in a hot path. On the other hand, if you need to refer them even 43 * after changing DOM tree, you must use EditorDOMPoint. 44 * 45 * When initializing an instance only with child node or offset, the instance 46 * starts to refer the child node or offset in the container. In this case, 47 * the other information hasn't been initialized due to performance reason. 48 * When you retrieve the other information with calling Offset() or 49 * GetChild(), the other information is computed with the current DOM tree. 50 * Therefore, e.g., in the following case, the other information may be 51 * different: 52 * 53 * EditorDOMPoint pointA(container1, childNode1); 54 * EditorDOMPoint pointB(container1, childNode1); 55 * (void)pointA.Offset(); // The offset is computed now. 56 * container1->RemoveChild(childNode1->GetPreviousSibling()); 57 * (void)pointB.Offset(); // Now, pointB.Offset() equals pointA.Offset() - 1 58 * 59 * similarly: 60 * 61 * EditorDOMPoint pointA(container1, 5); 62 * EditorDOMPoint pointB(container1, 5); 63 * (void)pointA.GetChild(); // The child is computed now. 64 * container1->RemoveChild(childNode1->GetFirstChild()); 65 * (void)pointB.GetChild(); // Now, pointB.GetChild() equals 66 * // pointA.GetChild()->GetPreviousSibling(). 67 * 68 * So, when you initialize an instance only with one information, you need to 69 * be careful when you access the other information after changing the DOM tree. 70 * When you need to lock the child node or offset and recompute the other 71 * information with new DOM tree, you can use 72 * AutoEditorDOMPointOffsetInvalidator and AutoEditorDOMPointChildInvalidator. 73 */ 74 75 // FYI: Don't make the following instantiating macros end with `;` because 76 // using them without `;`, VSCode may be confused and cause wrong red- 77 // wavy underlines in the following code of the macro. 78 #define NS_INSTANTIATE_EDITOR_DOM_POINT_METHOD(aResultType, aMethodName, ...) \ 79 template aResultType EditorDOMPoint::aMethodName(__VA_ARGS__); \ 80 template aResultType EditorRawDOMPoint::aMethodName(__VA_ARGS__); \ 81 template aResultType EditorDOMPointInText::aMethodName(__VA_ARGS__); \ 82 template aResultType EditorRawDOMPointInText::aMethodName(__VA_ARGS__) 83 84 #define NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(aResultType, aMethodName, \ 85 ...) \ 86 template aResultType EditorDOMPoint::aMethodName(__VA_ARGS__) const; \ 87 template aResultType EditorRawDOMPoint::aMethodName(__VA_ARGS__) const; \ 88 template aResultType EditorDOMPointInText::aMethodName(__VA_ARGS__) const; \ 89 template aResultType EditorRawDOMPointInText::aMethodName(__VA_ARGS__) const 90 91 #define NS_INSTANTIATE_METHOD_RETURNING_ANY_EDITOR_DOM_POINT(aMethodName, ...) \ 92 template EditorDOMPoint aMethodName(__VA_ARGS__); \ 93 template EditorRawDOMPoint aMethodName(__VA_ARGS__); \ 94 template EditorDOMPointInText aMethodName(__VA_ARGS__); \ 95 template EditorRawDOMPointInText aMethodName(__VA_ARGS__) 96 97 #define NS_INSTANTIATE_CONST_METHOD_RETURNING_ANY_EDITOR_DOM_POINT( \ 98 aMethodName, ...) \ 99 template EditorDOMPoint aMethodName(__VA_ARGS__) const; \ 100 template EditorRawDOMPoint aMethodName(__VA_ARGS__) const; \ 101 template EditorDOMPointInText aMethodName(__VA_ARGS__) const; \ 102 template EditorRawDOMPointInText aMethodName(__VA_ARGS__) const 103 104 template <typename ParentType, typename ChildType> 105 class EditorDOMPointBase final { 106 using SelfType = EditorDOMPointBase<ParentType, ChildType>; 107 108 public: 109 using InterlinePosition = dom::Selection::InterlinePosition; 110 111 EditorDOMPointBase() = default; 112 113 template <typename ContainerType> 114 EditorDOMPointBase( 115 const ContainerType* aContainer, uint32_t aOffset, 116 InterlinePosition aInterlinePosition = InterlinePosition::Undefined) 117 : mParent(const_cast<ContainerType*>(aContainer)), 118 mChild(nullptr), 119 mOffset(Some(aOffset)), 120 mInterlinePosition(aInterlinePosition) { 121 NS_WARNING_ASSERTION( 122 !mParent || mOffset.value() <= mParent->Length(), 123 "The offset is larger than the length of aContainer or negative"); 124 if (!mParent) { 125 mOffset.reset(); 126 } 127 } 128 129 template <typename PT, template <typename> typename StrongPtr> 130 EditorDOMPointBase( 131 StrongPtr<PT>&& aContainer, uint32_t aOffset, 132 InterlinePosition aInterlinePosition = InterlinePosition::Undefined) 133 : mParent(std::forward<StrongPtr<PT>>(aContainer)), 134 mChild(nullptr), 135 mOffset(Some(aOffset)), 136 mInterlinePosition(aInterlinePosition) { 137 NS_WARNING_ASSERTION( 138 !mParent || mOffset.value() <= mParent->Length(), 139 "The offset is larger than the length of aContainer or negative"); 140 if (!mParent) { 141 mOffset.reset(); 142 } 143 } 144 145 template <typename ContainerType, template <typename> typename StrongPtr> 146 EditorDOMPointBase( 147 const StrongPtr<ContainerType>& aContainer, uint32_t aOffset, 148 InterlinePosition aInterlinePosition = InterlinePosition::Undefined) 149 : EditorDOMPointBase(aContainer.get(), aOffset, aInterlinePosition) {} 150 151 template <typename ContainerType, template <typename> typename StrongPtr> 152 EditorDOMPointBase( 153 const StrongPtr<const ContainerType>& aContainer, uint32_t aOffset, 154 InterlinePosition aInterlinePosition = InterlinePosition::Undefined) 155 : EditorDOMPointBase(aContainer.get(), aOffset, aInterlinePosition) {} 156 157 /** 158 * Different from RangeBoundary, aPointedNode should be a child node 159 * which you want to refer. 160 */ 161 explicit EditorDOMPointBase( 162 const nsINode* aPointedNode, // FIXME: This should const nsIContent& 163 InterlinePosition aInterlinePosition = InterlinePosition::Undefined) 164 : mParent(aPointedNode && aPointedNode->IsContent() 165 ? aPointedNode->GetParentNode() 166 : nullptr), 167 mChild(aPointedNode && aPointedNode->IsContent() 168 ? const_cast<nsIContent*>(aPointedNode->AsContent()) 169 : nullptr), 170 mInterlinePosition(aInterlinePosition) { 171 mIsChildInitialized = mChild; 172 NS_WARNING_ASSERTION(IsSet(), 173 "The child is nullptr or doesn't have its parent"); 174 NS_WARNING_ASSERTION(mChild && mChild->GetParentNode() == mParent, 175 "Initializing RangeBoundary with invalid value"); 176 } 177 178 template <typename CT, template <typename> typename StrongPtr> 179 explicit EditorDOMPointBase( 180 StrongPtr<CT>&& aChild, 181 InterlinePosition aInterlinePosition = InterlinePosition::Undefined) 182 : mParent(aChild ? aChild->GetParentNode() : nullptr), 183 mChild(std::forward<StrongPtr<CT>>(aChild)), 184 mInterlinePosition(aInterlinePosition) { 185 mIsChildInitialized = !!mChild; 186 NS_WARNING_ASSERTION(IsSet(), 187 "The child is nullptr or doesn't have its parent"); 188 NS_WARNING_ASSERTION(mChild && mChild->GetParentNode() == mParent, 189 "Initializing RangeBoundary with invalid value"); 190 } 191 192 EditorDOMPointBase( 193 nsINode* aContainer, nsIContent* aPointedNode, uint32_t aOffset, 194 InterlinePosition aInterlinePosition = InterlinePosition::Undefined) 195 : mParent(aContainer), 196 mChild(aPointedNode), 197 mOffset(mozilla::Some(aOffset)), 198 mInterlinePosition(aInterlinePosition), 199 mIsChildInitialized(true) { 200 MOZ_DIAGNOSTIC_ASSERT( 201 aContainer, "This constructor shouldn't be used when pointing nowhere"); 202 MOZ_ASSERT(mOffset.value() <= mParent->Length()); 203 MOZ_ASSERT(mChild || mParent->Length() == mOffset.value() || 204 !mParent->IsContainerNode()); 205 MOZ_ASSERT(!mChild || mParent == mChild->GetParentNode()); 206 MOZ_ASSERT(mParent->GetChildAt_Deprecated(mOffset.value()) == mChild); 207 } 208 209 template <typename PT, typename CT> 210 explicit EditorDOMPointBase(const RangeBoundaryBase<PT, CT>& aOther) 211 : mParent(aOther.mParent), 212 mChild(aOther.mRef ? aOther.mRef->GetNextSibling() 213 : (aOther.mParent ? aOther.mParent->GetFirstChild() 214 : nullptr)), 215 mOffset(aOther.mOffset), 216 mIsChildInitialized(aOther.mRef || (aOther.mOffset.isSome() && 217 !aOther.mOffset.value())) {} 218 219 void SetInterlinePosition(InterlinePosition aInterlinePosition) { 220 MOZ_ASSERT(IsSet()); 221 mInterlinePosition = aInterlinePosition; 222 } 223 InterlinePosition GetInterlinePosition() const { 224 return IsSet() ? mInterlinePosition : InterlinePosition::Undefined; 225 } 226 227 /** 228 * GetContainer() returns the container node at the point. 229 * GetContainerAs() returns the container node as specific type. 230 */ 231 nsINode* GetContainer() const { return mParent; } 232 template <typename ContentNodeType> 233 ContentNodeType* GetContainerAs() const { 234 return ContentNodeType::FromNodeOrNull(mParent); 235 } 236 237 /** 238 * ContainerAs() returns the container node with just casting to the specific 239 * type. Therefore, callers need to guarantee that the result is not nullptr 240 * nor wrong cast. 241 */ 242 template <typename ContentNodeType> 243 ContentNodeType* ContainerAs() const { 244 MOZ_ASSERT(mParent); 245 MOZ_DIAGNOSTIC_ASSERT( 246 ContentNodeType::FromNode(static_cast<const nsINode*>(mParent))); 247 return static_cast<ContentNodeType*>(GetContainer()); 248 } 249 250 /** 251 * GetContainerParent() returns parent of the container node at the point. 252 */ 253 nsINode* GetContainerParent() const { 254 return mParent ? mParent->GetParent() : nullptr; 255 } 256 template <typename ContentNodeType> 257 ContentNodeType* GetContainerParentAs() const { 258 return ContentNodeType::FromNodeOrNull(GetContainerParent()); 259 } 260 template <typename ContentNodeType> 261 ContentNodeType* ContainerParentAs() const { 262 MOZ_DIAGNOSTIC_ASSERT(GetContainerParentAs<ContentNodeType>()); 263 return static_cast<ContentNodeType*>(GetContainerParent()); 264 } 265 266 dom::Element* GetContainerOrContainerParentElement() const { 267 if (MOZ_UNLIKELY(!mParent)) { 268 return nullptr; 269 } 270 return mParent->IsElement() ? ContainerAs<dom::Element>() 271 : GetContainerParentAs<dom::Element>(); 272 } 273 274 /** 275 * CanContainerHaveChildren() returns true if the container node can have 276 * child nodes. Otherwise, e.g., when the container is a text node, returns 277 * false. 278 */ 279 bool CanContainerHaveChildren() const { 280 return mParent && mParent->IsContainerNode(); 281 } 282 283 /** 284 * IsContainerEmpty() returns true if it has no children or its text is empty. 285 */ 286 bool IsContainerEmpty() const { return mParent && !mParent->Length(); } 287 288 /** 289 * IsInContentNode() returns true if the container is a subclass of 290 * nsIContent. 291 */ 292 bool IsInContentNode() const { return mParent && mParent->IsContent(); } 293 294 /** 295 * IsInDataNode() returns true if the container node is a data node including 296 * text node. 297 */ 298 bool IsInDataNode() const { return mParent && mParent->IsCharacterData(); } 299 300 /** 301 * IsInTextNode() returns true if the container node is a text node. 302 */ 303 bool IsInTextNode() const { return mParent && mParent->IsText(); } 304 305 /** 306 * IsInNativeAnonymousSubtree() returns true if the container is in 307 * native anonymous subtree. 308 */ 309 bool IsInNativeAnonymousSubtree() const { 310 return mParent && mParent->IsInNativeAnonymousSubtree(); 311 } 312 313 /** 314 * Returns true if the container node is an element node. 315 */ 316 bool IsContainerElement() const { return mParent && mParent->IsElement(); } 317 318 /** 319 * Returns true if the container node is an editing host. 320 */ 321 [[nodiscard]] bool IsContainerEditableRoot() const; 322 323 /** 324 * IsContainerHTMLElement() returns true if the container node is an HTML 325 * element node and its node name is aTag. 326 */ 327 bool IsContainerHTMLElement(nsAtom* aTag) const { 328 return mParent && mParent->IsHTMLElement(aTag); 329 } 330 331 /** 332 * IsContainerAnyOfHTMLElements() returns true if the container node is an 333 * HTML element node and its node name is one of the arguments. 334 */ 335 template <typename First, typename... Args> 336 bool IsContainerAnyOfHTMLElements(First aFirst, Args... aArgs) const { 337 return mParent && mParent->IsAnyOfHTMLElements(aFirst, aArgs...); 338 } 339 340 /** 341 * GetChild() returns a child node which is pointed by the instance. 342 * If mChild hasn't been initialized yet, this computes the child node 343 * from mParent and mOffset with *current* DOM tree. 344 */ 345 nsIContent* GetChild() const { 346 if (!mParent || !mParent->IsContainerNode()) { 347 return nullptr; 348 } 349 if (mIsChildInitialized) { 350 return mChild; 351 } 352 // Fix child node now. 353 const_cast<SelfType*>(this)->EnsureChild(); 354 return mChild; 355 } 356 357 template <typename ContentNodeType> 358 ContentNodeType* GetChildAs() const { 359 return ContentNodeType::FromNodeOrNull(GetChild()); 360 } 361 template <typename ContentNodeType> 362 ContentNodeType* ChildAs() const { 363 MOZ_DIAGNOSTIC_ASSERT(GetChildAs<ContentNodeType>()); 364 return static_cast<ContentNodeType*>(GetChild()); 365 } 366 367 /** 368 * GetCurrentChildAtOffset() returns current child at mOffset. 369 * I.e., mOffset needs to be fixed before calling this. 370 */ 371 nsIContent* GetCurrentChildAtOffset() const { 372 MOZ_ASSERT(mOffset.isSome()); 373 if (mOffset.isNothing()) { 374 return GetChild(); 375 } 376 return mParent ? mParent->GetChildAt_Deprecated(*mOffset) : nullptr; 377 } 378 379 /** 380 * GetChildOrContainerIfDataNode() returns the child content node, 381 * or container content node if the container is a data node. 382 */ 383 nsIContent* GetChildOrContainerIfDataNode() const { 384 if (IsInDataNode()) { 385 return ContainerAs<nsIContent>(); 386 } 387 return GetChild(); 388 } 389 390 /** 391 * GetNextSiblingOfChild() returns next sibling of the child node. 392 * If this refers after the last child or the container cannot have children, 393 * this returns nullptr with warning. 394 * If mChild hasn't been initialized yet, this computes the child node 395 * from mParent and mOffset with *current* DOM tree. 396 */ 397 nsIContent* GetNextSiblingOfChild() const { 398 if (NS_WARN_IF(!mParent) || !mParent->IsContainerNode()) { 399 return nullptr; 400 } 401 if (mIsChildInitialized) { 402 return mChild ? mChild->GetNextSibling() : nullptr; 403 } 404 MOZ_ASSERT(mOffset.isSome()); 405 if (NS_WARN_IF(mOffset.value() > mParent->Length())) { 406 // If this has been set only offset and now the offset is invalid, 407 // let's just return nullptr. 408 return nullptr; 409 } 410 // Fix child node now. 411 const_cast<SelfType*>(this)->EnsureChild(); 412 return mChild ? mChild->GetNextSibling() : nullptr; 413 } 414 template <typename ContentNodeType> 415 ContentNodeType* GetNextSiblingOfChildAs() const { 416 return ContentNodeType::FromNodeOrNull(GetNextSiblingOfChild()); 417 } 418 template <typename ContentNodeType> 419 ContentNodeType* NextSiblingOfChildAs() const { 420 MOZ_ASSERT(IsSet()); 421 MOZ_DIAGNOSTIC_ASSERT(GetNextSiblingOfChildAs<ContentNodeType>()); 422 return static_cast<ContentNodeType*>(GetNextSiblingOfChild()); 423 } 424 425 /** 426 * GetPreviousSiblingOfChild() returns previous sibling of a child 427 * at offset. If this refers the first child or the container cannot have 428 * children, this returns nullptr with warning. 429 * If mChild hasn't been initialized yet, this computes the child node 430 * from mParent and mOffset with *current* DOM tree. 431 */ 432 nsIContent* GetPreviousSiblingOfChild() const { 433 if (NS_WARN_IF(!mParent) || !mParent->IsContainerNode()) { 434 return nullptr; 435 } 436 if (mIsChildInitialized) { 437 return mChild ? mChild->GetPreviousSibling() : mParent->GetLastChild(); 438 } 439 MOZ_ASSERT(mOffset.isSome()); 440 if (NS_WARN_IF(mOffset.value() > mParent->Length())) { 441 // If this has been set only offset and now the offset is invalid, 442 // let's just return nullptr. 443 return nullptr; 444 } 445 // Fix child node now. 446 const_cast<SelfType*>(this)->EnsureChild(); 447 return mChild ? mChild->GetPreviousSibling() : mParent->GetLastChild(); 448 } 449 template <typename ContentNodeType> 450 ContentNodeType* GetPreviousSiblingOfChildAs() const { 451 return ContentNodeType::FromNodeOrNull(GetPreviousSiblingOfChild()); 452 } 453 template <typename ContentNodeType> 454 ContentNodeType* PreviousSiblingOfChildAs() const { 455 MOZ_ASSERT(IsSet()); 456 MOZ_DIAGNOSTIC_ASSERT(GetPreviousSiblingOfChildAs<ContentNodeType>()); 457 return static_cast<ContentNodeType*>(GetPreviousSiblingOfChild()); 458 } 459 460 /** 461 * Simple accessors of the character in dom::Text so that when you call 462 * these methods, you need to guarantee that the container is a dom::Text. 463 */ 464 MOZ_NEVER_INLINE_DEBUG char16_t Char() const { 465 MOZ_ASSERT(IsSetAndValid()); 466 MOZ_ASSERT(!IsEndOfContainer()); 467 return ContainerAs<dom::Text>()->DataBuffer().CharAt(mOffset.value()); 468 } 469 MOZ_NEVER_INLINE_DEBUG bool IsCharASCIISpace() const { 470 return nsCRT::IsAsciiSpace(Char()); 471 } 472 MOZ_NEVER_INLINE_DEBUG bool IsCharNBSP() const { return Char() == 0x00A0; } 473 MOZ_NEVER_INLINE_DEBUG bool IsCharASCIISpaceOrNBSP() const { 474 char16_t ch = Char(); 475 return nsCRT::IsAsciiSpace(ch) || ch == 0x00A0; 476 } 477 MOZ_NEVER_INLINE_DEBUG bool IsCharNewLine() const { return Char() == '\n'; } 478 479 /** 480 * Return true if pointing "\n" and it should be rendered as a line break. 481 * Be aware that even if this returns false, the "\n" may be rendered as a 482 * non-collapsible white-space. 483 */ 484 MOZ_NEVER_INLINE_DEBUG bool IsCharPreformattedNewLine() const; 485 486 MOZ_NEVER_INLINE_DEBUG bool 487 IsCharPreformattedNewLineCollapsedWithWhiteSpaces() const; 488 489 /** 490 * IsCharCollapsibleASCIISpace(), IsCharCollapsibleNBSP() and 491 * IsCharCollapsibleASCIISpaceOrNBSP() checks whether the white-space is 492 * preformatted or collapsible with the style of the container text node 493 * without flushing pending notifications. 494 */ 495 bool IsCharCollapsibleASCIISpace() const; 496 bool IsCharCollapsibleNBSP() const; 497 bool IsCharCollapsibleASCIISpaceOrNBSP() const; 498 499 MOZ_NEVER_INLINE_DEBUG bool IsCharHighSurrogateFollowedByLowSurrogate() 500 const { 501 MOZ_ASSERT(IsSetAndValid()); 502 MOZ_ASSERT(!IsEndOfContainer()); 503 return ContainerAs<dom::Text>() 504 ->DataBuffer() 505 .IsHighSurrogateFollowedByLowSurrogateAt(mOffset.value()); 506 } 507 MOZ_NEVER_INLINE_DEBUG bool IsCharLowSurrogateFollowingHighSurrogate() const { 508 MOZ_ASSERT(IsSetAndValid()); 509 MOZ_ASSERT(!IsEndOfContainer()); 510 return ContainerAs<dom::Text>() 511 ->DataBuffer() 512 .IsLowSurrogateFollowingHighSurrogateAt(mOffset.value()); 513 } 514 515 MOZ_NEVER_INLINE_DEBUG char16_t PreviousChar() const { 516 MOZ_ASSERT(IsSetAndValid()); 517 MOZ_ASSERT(!IsStartOfContainer()); 518 return ContainerAs<dom::Text>()->DataBuffer().CharAt(mOffset.value() - 1); 519 } 520 MOZ_NEVER_INLINE_DEBUG bool IsPreviousCharASCIISpace() const { 521 return nsCRT::IsAsciiSpace(PreviousChar()); 522 } 523 MOZ_NEVER_INLINE_DEBUG bool IsPreviousCharNBSP() const { 524 return PreviousChar() == 0x00A0; 525 } 526 MOZ_NEVER_INLINE_DEBUG bool IsPreviousCharASCIISpaceOrNBSP() const { 527 char16_t ch = PreviousChar(); 528 return nsCRT::IsAsciiSpace(ch) || ch == 0x00A0; 529 } 530 MOZ_NEVER_INLINE_DEBUG bool IsPreviousCharNewLine() const { 531 return PreviousChar() == '\n'; 532 } 533 534 /** 535 * Return true if pointing next character of "\n" and it should be rendered as 536 * a line break. Be aware that even if this returns false, the "\n" may be 537 * rendered as a non-collapsible white-space. 538 */ 539 MOZ_NEVER_INLINE_DEBUG bool IsPreviousCharPreformattedNewLine() const; 540 541 MOZ_NEVER_INLINE_DEBUG bool 542 IsPreviousCharPreformattedNewLineCollapsedWithWhiteSpaces() const; 543 544 /** 545 * IsPreviousCharCollapsibleASCIISpace(), IsPreviousCharCollapsibleNBSP() and 546 * IsPreviousCharCollapsibleASCIISpaceOrNBSP() checks whether the white-space 547 * is preformatted or collapsible with the style of the container text node 548 * without flushing pending notifications. 549 */ 550 bool IsPreviousCharCollapsibleASCIISpace() const; 551 bool IsPreviousCharCollapsibleNBSP() const; 552 bool IsPreviousCharCollapsibleASCIISpaceOrNBSP() const; 553 554 MOZ_NEVER_INLINE_DEBUG char16_t NextChar() const { 555 MOZ_ASSERT(IsSetAndValid()); 556 MOZ_ASSERT(!IsAtLastContent() && !IsEndOfContainer()); 557 return ContainerAs<dom::Text>()->DataBuffer().CharAt(mOffset.value() + 1); 558 } 559 MOZ_NEVER_INLINE_DEBUG bool IsNextCharASCIISpace() const { 560 return nsCRT::IsAsciiSpace(NextChar()); 561 } 562 MOZ_NEVER_INLINE_DEBUG bool IsNextCharNBSP() const { 563 return NextChar() == 0x00A0; 564 } 565 MOZ_NEVER_INLINE_DEBUG bool IsNextCharASCIISpaceOrNBSP() const { 566 char16_t ch = NextChar(); 567 return nsCRT::IsAsciiSpace(ch) || ch == 0x00A0; 568 } 569 MOZ_NEVER_INLINE_DEBUG bool IsNextCharNewLine() const { 570 return NextChar() == '\n'; 571 } 572 573 /** 574 * Return true if pointing previous character of "\n" and it should be 575 * rendered as a line break. Be aware that even if this returns false, the 576 * "\n" may be rendered as a non-collapsible white-space. 577 */ 578 MOZ_NEVER_INLINE_DEBUG bool IsNextCharPreformattedNewLine() const; 579 580 MOZ_NEVER_INLINE_DEBUG bool 581 IsNextCharPreformattedNewLineCollapsedWithWhiteSpaces() const; 582 583 /** 584 * IsNextCharCollapsibleASCIISpace(), IsNextCharCollapsibleNBSP() and 585 * IsNextCharCollapsibleASCIISpaceOrNBSP() checks whether the white-space is 586 * preformatted or collapsible with the style of the container text node 587 * without flushing pending notifications. 588 */ 589 bool IsNextCharCollapsibleASCIISpace() const; 590 bool IsNextCharCollapsibleNBSP() const; 591 bool IsNextCharCollapsibleASCIISpaceOrNBSP() const; 592 593 [[nodiscard]] bool HasOffset() const { return mOffset.isSome(); } 594 uint32_t Offset() const { 595 if (mOffset.isSome()) { 596 MOZ_ASSERT(mOffset.isSome()); 597 return mOffset.value(); 598 } 599 if (MOZ_UNLIKELY(!mParent)) { 600 MOZ_ASSERT(!mChild); 601 return 0u; 602 } 603 MOZ_ASSERT(mParent->IsContainerNode(), 604 "If the container cannot have children, mOffset.isSome() should " 605 "be true"); 606 if (!mChild) { 607 // We're referring after the last child. Fix offset now. 608 const_cast<SelfType*>(this)->mOffset = mozilla::Some(mParent->Length()); 609 return mOffset.value(); 610 } 611 MOZ_ASSERT(mChild->GetParentNode() == mParent); 612 // Fix offset now. 613 if (mChild == mParent->GetFirstChild()) { 614 const_cast<SelfType*>(this)->mOffset = mozilla::Some(0u); 615 return 0u; 616 } 617 const_cast<SelfType*>(this)->mOffset = mParent->ComputeIndexOf(mChild); 618 MOZ_DIAGNOSTIC_ASSERT(mOffset.isSome()); 619 return mOffset.valueOr(0u); // Avoid crash in Release/Beta 620 } 621 622 /** 623 * Set() sets a point to aOffset or aChild. 624 * If it's set with aOffset, mChild is invalidated. If it's set with aChild, 625 * mOffset may be invalidated. 626 */ 627 template <typename ContainerType> 628 void Set(ContainerType* aContainer, uint32_t aOffset) { 629 mParent = aContainer; 630 mChild = nullptr; 631 mOffset = mozilla::Some(aOffset); 632 mIsChildInitialized = false; 633 mInterlinePosition = InterlinePosition::Undefined; 634 NS_ASSERTION(!mParent || mOffset.value() <= mParent->Length(), 635 "The offset is out of bounds"); 636 } 637 template <typename ContainerType, template <typename> typename StrongPtr> 638 void Set(const StrongPtr<ContainerType>& aContainer, uint32_t aOffset) { 639 Set(aContainer.get(), aOffset); 640 } 641 template <typename ContainerType, template <typename> typename StrongPtr> 642 void Set(StrongPtr<ContainerType>&& aContainer, uint32_t aOffset) { 643 mParent = std::forward<StrongPtr<ContainerType>>(aContainer); 644 mChild = nullptr; 645 mOffset = mozilla::Some(aOffset); 646 mIsChildInitialized = false; 647 mInterlinePosition = InterlinePosition::Undefined; 648 NS_ASSERTION(!mParent || mOffset.value() <= mParent->Length(), 649 "The offset is out of bounds"); 650 } 651 void Set(const nsINode* aChild) { 652 MOZ_ASSERT(aChild); 653 if (NS_WARN_IF(!aChild->IsContent())) { 654 Clear(); 655 return; 656 } 657 mParent = aChild->GetParentNode(); 658 mChild = const_cast<nsIContent*>(aChild->AsContent()); 659 mOffset.reset(); 660 mIsChildInitialized = true; 661 mInterlinePosition = InterlinePosition::Undefined; 662 } 663 template <typename CT, template <typename> typename StrongPtr> 664 void Set(StrongPtr<CT>&& aChild) { 665 MOZ_ASSERT(aChild); 666 if (NS_WARN_IF(!aChild->IsContent())) { 667 Clear(); 668 return; 669 } 670 mParent = aChild->GetParentNode(); 671 mChild = std::forward<StrongPtr<CT>>(aChild); 672 mOffset.reset(); 673 mIsChildInitialized = true; 674 mInterlinePosition = InterlinePosition::Undefined; 675 } 676 677 /** 678 * SetToEndOf() sets this to the end of aContainer. Then, mChild is always 679 * nullptr but marked as initialized and mOffset is always set. 680 */ 681 template <typename ContainerType> 682 void SetToEndOf(const ContainerType* aContainer) { 683 MOZ_ASSERT(aContainer); 684 mParent = const_cast<ContainerType*>(aContainer); 685 mChild = nullptr; 686 mOffset = mozilla::Some(mParent->Length()); 687 mIsChildInitialized = true; 688 mInterlinePosition = InterlinePosition::Undefined; 689 } 690 template <typename ContainerType, template <typename> typename StrongPtr> 691 void SetToEndOf(const StrongPtr<ContainerType>& aContainer) { 692 SetToEndOf(aContainer.get()); 693 } 694 template <typename ContainerType, template <typename> typename StrongPtr> 695 void SetToEndOf(StrongPtr<ContainerType>&& aContainer) { 696 mParent = std::forward<StrongPtr<ContainerType>>(aContainer); 697 mChild = nullptr; 698 mOffset = mozilla::Some(mParent->Length()); 699 mIsChildInitialized = true; 700 mInterlinePosition = InterlinePosition::Undefined; 701 } 702 template <typename ContainerType> 703 [[nodiscard]] static SelfType AtEndOf( 704 const ContainerType& aContainer, 705 InterlinePosition aInterlinePosition = InterlinePosition::Undefined) { 706 SelfType point; 707 point.SetToEndOf(&aContainer); 708 point.mInterlinePosition = aInterlinePosition; 709 return point; 710 } 711 template <typename ContainerType, template <typename> typename StrongPtr> 712 [[nodiscard]] static SelfType AtEndOf( 713 const StrongPtr<ContainerType>& aContainer, 714 InterlinePosition aInterlinePosition = InterlinePosition::Undefined) { 715 MOZ_ASSERT(aContainer.get()); 716 return AtEndOf(*aContainer.get(), aInterlinePosition); 717 } 718 template <typename ContainerType, template <typename> typename StrongPtr> 719 [[nodiscard]] static SelfType AtEndOf( 720 StrongPtr<ContainerType>&& aContainer, 721 InterlinePosition aInterlinePosition = InterlinePosition::Undefined) { 722 MOZ_ASSERT(aContainer.get()); 723 SelfType result; 724 result.SetToEndOf(std::forward<StrongPtr<ContainerType>>(aContainer)); 725 result.mInterlinePosition = aInterlinePosition; 726 return result; 727 } 728 729 /** 730 * SetToLastContentOf() sets this to the last child of aContainer or the last 731 * character of aContainer. 732 */ 733 template <typename ContainerType> 734 void SetToLastContentOf(const ContainerType* aContainer) { 735 MOZ_ASSERT(aContainer); 736 mParent = const_cast<ContainerType*>(aContainer); 737 if (aContainer->IsContainerNode()) { 738 MOZ_ASSERT(aContainer->GetChildCount()); 739 mChild = aContainer->GetLastChild(); 740 mOffset = mozilla::Some(aContainer->GetChildCount() - 1u); 741 } else { 742 MOZ_ASSERT(aContainer->Length()); 743 mChild = nullptr; 744 mOffset = mozilla::Some(aContainer->Length() - 1u); 745 } 746 mIsChildInitialized = true; 747 mInterlinePosition = InterlinePosition::Undefined; 748 } 749 template <typename ContainerType, template <typename> typename StrongPtr> 750 MOZ_NEVER_INLINE_DEBUG void SetToLastContentOf( 751 const StrongPtr<ContainerType>& aContainer) { 752 SetToLastContentOf(aContainer.get()); 753 } 754 template <typename ContainerType> 755 MOZ_NEVER_INLINE_DEBUG static SelfType AtLastContentOf( 756 const ContainerType& aContainer, 757 InterlinePosition aInterlinePosition = InterlinePosition::Undefined) { 758 SelfType point; 759 point.SetToLastContentOf(&aContainer); 760 point.mInterlinePosition = aInterlinePosition; 761 return point; 762 } 763 template <typename ContainerType, template <typename> typename StrongPtr> 764 MOZ_NEVER_INLINE_DEBUG static SelfType AtLastContentOf( 765 const StrongPtr<ContainerType>& aContainer, 766 InterlinePosition aInterlinePosition = InterlinePosition::Undefined) { 767 MOZ_ASSERT(aContainer.get()); 768 return AtLastContentOf(*aContainer.get(), aInterlinePosition); 769 } 770 771 /** 772 * SetAfter() sets mChild to next sibling of aChild. 773 */ 774 void SetAfter(const nsINode* aChild) { 775 MOZ_ASSERT(aChild); 776 nsIContent* nextSibling = aChild->GetNextSibling(); 777 if (nextSibling) { 778 Set(nextSibling); 779 return; 780 } 781 nsINode* parentNode = aChild->GetParentNode(); 782 if (NS_WARN_IF(!parentNode)) { 783 Clear(); 784 return; 785 } 786 SetToEndOf(parentNode); 787 } 788 void SetAfterContainer() { 789 MOZ_ASSERT(mParent); 790 SetAfter(mParent); 791 } 792 template <typename ContainerType> 793 static SelfType After( 794 const ContainerType& aContainer, 795 InterlinePosition aInterlinePosition = InterlinePosition::Undefined) { 796 SelfType point; 797 point.SetAfter(&aContainer); 798 point.mInterlinePosition = aInterlinePosition; 799 return point; 800 } 801 template <typename ContainerType, template <typename> typename StrongPtr> 802 MOZ_NEVER_INLINE_DEBUG static SelfType After( 803 const StrongPtr<ContainerType>& aContainer, 804 InterlinePosition aInterlinePosition = InterlinePosition::Undefined) { 805 MOZ_ASSERT(aContainer.get()); 806 return After(*aContainer.get(), aInterlinePosition); 807 } 808 template <typename PT, typename CT> 809 MOZ_NEVER_INLINE_DEBUG static SelfType After( 810 const EditorDOMPointBase<PT, CT>& aPoint, 811 InterlinePosition aInterlinePosition = InterlinePosition::Undefined) { 812 MOZ_ASSERT(aPoint.IsSet()); 813 if (aPoint.mChild) { 814 return After(*aPoint.mChild, aInterlinePosition); 815 } 816 if (NS_WARN_IF(aPoint.IsEndOfContainer())) { 817 return SelfType(); 818 } 819 auto point = aPoint.NextPoint().template To<SelfType>(); 820 point.mInterlinePosition = aInterlinePosition; 821 return point; 822 } 823 824 /** 825 * ParentPoint() returns a point whose child is the container. 826 */ 827 template <typename EditorDOMPointType = SelfType> 828 EditorDOMPointType ParentPoint() const { 829 MOZ_ASSERT(mParent); 830 if (MOZ_UNLIKELY(!mParent) || !mParent->IsContent()) { 831 return EditorDOMPointType(); 832 } 833 return EditorDOMPointType(ContainerAs<nsIContent>()); 834 } 835 836 /** 837 * NextPoint() and PreviousPoint() returns next/previous DOM point in 838 * the container. 839 */ 840 template <typename EditorDOMPointType = SelfType> 841 EditorDOMPointType NextPoint() const { 842 NS_ASSERTION(!IsEndOfContainer(), "Should not be at end of the container"); 843 auto result = this->template To<EditorDOMPointType>(); 844 result.AdvanceOffset(); 845 return result; 846 } 847 template <typename EditorDOMPointType = SelfType> 848 EditorDOMPointType NextPointOrAfterContainer() const { 849 MOZ_ASSERT(IsInContentNode()); 850 if (!IsEndOfContainer()) { 851 return NextPoint<EditorDOMPointType>(); 852 } 853 return AfterContainer<EditorDOMPointType>(); 854 } 855 template <typename EditorDOMPointType = SelfType> 856 EditorDOMPointType AfterContainer() const { 857 MOZ_ASSERT(IsInContentNode()); 858 return EditorDOMPointType::After(*ContainerAs<nsIContent>()); 859 } 860 template <typename EditorDOMPointType = SelfType> 861 EditorDOMPointType PreviousPoint() const { 862 NS_ASSERTION(!IsStartOfContainer(), 863 "Should not be at start of the container"); 864 auto result = this->template To<EditorDOMPointType>(); 865 result.RewindOffset(); 866 return result; 867 } 868 template <typename EditorDOMPointType = SelfType> 869 EditorDOMPointType PreviousPointOrParentPoint() const { 870 if (IsStartOfContainer()) { 871 return ParentPoint<EditorDOMPointType>(); 872 } 873 return PreviousPoint<EditorDOMPointType>(); 874 } 875 876 /** 877 * Clear() makes the instance not point anywhere. 878 */ 879 void Clear() { 880 mParent = nullptr; 881 mChild = nullptr; 882 mOffset.reset(); 883 mIsChildInitialized = false; 884 mInterlinePosition = InterlinePosition::Undefined; 885 } 886 887 /** 888 * AdvanceOffset() tries to refer next sibling of mChild and/of next offset. 889 * If the container can have children and there is no next sibling or the 890 * offset reached the length of the container, this outputs warning and does 891 * nothing. So, callers need to check if there is next sibling which you 892 * need to refer. 893 * 894 * @return true if there is a next DOM point to refer. 895 */ 896 bool AdvanceOffset() { 897 if (NS_WARN_IF(!mParent)) { 898 return false; 899 } 900 // If only mOffset is available, just compute the offset. 901 if ((mOffset.isSome() && !mIsChildInitialized) || 902 !mParent->IsContainerNode()) { 903 MOZ_ASSERT(mOffset.isSome()); 904 MOZ_ASSERT(!mChild); 905 if (NS_WARN_IF(mOffset.value() >= mParent->Length())) { 906 // We're already referring the start of the container. 907 return false; 908 } 909 mOffset = mozilla::Some(mOffset.value() + 1); 910 mInterlinePosition = InterlinePosition::Undefined; 911 return true; 912 } 913 914 MOZ_ASSERT(mIsChildInitialized); 915 MOZ_ASSERT(!mOffset.isSome() || mOffset.isSome()); 916 if (NS_WARN_IF(!mParent->HasChildren()) || NS_WARN_IF(!mChild) || 917 NS_WARN_IF(mOffset.isSome() && mOffset.value() >= mParent->Length())) { 918 // We're already referring the end of the container (or outside). 919 return false; 920 } 921 922 if (mOffset.isSome()) { 923 MOZ_ASSERT(mOffset.isSome()); 924 mOffset = mozilla::Some(mOffset.value() + 1); 925 } 926 mChild = mChild->GetNextSibling(); 927 mInterlinePosition = InterlinePosition::Undefined; 928 return true; 929 } 930 931 /** 932 * RewindOffset() tries to refer previous sibling of mChild and/or previous 933 * offset. If the container can have children and there is no next previous 934 * or the offset is 0, this outputs warning and does nothing. So, callers 935 * need to check if there is previous sibling which you need to refer. 936 * 937 * @return true if there is a previous DOM point to refer. 938 */ 939 bool RewindOffset() { 940 if (NS_WARN_IF(!mParent)) { 941 return false; 942 } 943 // If only mOffset is available, just compute the offset. 944 if ((mOffset.isSome() && !mIsChildInitialized) || 945 !mParent->IsContainerNode()) { 946 MOZ_ASSERT(mOffset.isSome()); 947 MOZ_ASSERT(!mChild); 948 if (NS_WARN_IF(!mOffset.value()) || 949 NS_WARN_IF(mOffset.value() > mParent->Length())) { 950 // We're already referring the start of the container or 951 // the offset is invalid since perhaps, the offset was set before 952 // the last DOM tree change. 953 NS_ASSERTION(false, "Failed to rewind offset"); 954 return false; 955 } 956 mOffset = mozilla::Some(mOffset.value() - 1); 957 mInterlinePosition = InterlinePosition::Undefined; 958 return true; 959 } 960 961 MOZ_ASSERT(mIsChildInitialized); 962 MOZ_ASSERT(!mOffset.isSome() || mOffset.isSome()); 963 if (NS_WARN_IF(!mParent->HasChildren()) || 964 NS_WARN_IF(mChild && !mChild->GetPreviousSibling()) || 965 NS_WARN_IF(mOffset.isSome() && !mOffset.value())) { 966 // We're already referring the start of the container (or the child has 967 // been moved from the container?). 968 return false; 969 } 970 971 nsIContent* previousSibling = 972 mChild ? mChild->GetPreviousSibling() : mParent->GetLastChild(); 973 if (NS_WARN_IF(!previousSibling)) { 974 // We're already referring the first child of the container. 975 return false; 976 } 977 978 if (mOffset.isSome()) { 979 mOffset = mozilla::Some(mOffset.value() - 1); 980 } 981 mChild = previousSibling; 982 mInterlinePosition = InterlinePosition::Undefined; 983 return true; 984 } 985 986 /** 987 * GetNonAnonymousSubtreePoint() returns a DOM point which is NOT in 988 * native-anonymous subtree. If the instance isn't in native-anonymous 989 * subtree, this returns same point. Otherwise, climbs up until finding 990 * non-native-anonymous parent and returns the point of it. I.e., 991 * container is parent of the found non-anonymous-native node. 992 */ 993 template <typename EditorDOMPointType> 994 EditorDOMPointType GetNonAnonymousSubtreePoint() const { 995 if (NS_WARN_IF(!IsSet())) { 996 return EditorDOMPointType(); 997 } 998 if (!IsInNativeAnonymousSubtree()) { 999 return this->template To<EditorDOMPointType>(); 1000 } 1001 nsINode* parent; 1002 for (parent = mParent->GetParentNode(); 1003 parent && parent->IsInNativeAnonymousSubtree(); 1004 parent = parent->GetParentNode()) { 1005 } 1006 if (!parent) { 1007 return EditorDOMPointType(); 1008 } 1009 return EditorDOMPointType(parent); 1010 } 1011 1012 [[nodiscard]] bool IsSet() const { 1013 return mParent && (mIsChildInitialized || mOffset.isSome()); 1014 } 1015 1016 [[nodiscard]] bool IsSetAndValid() const { 1017 if (!IsSet()) { 1018 return false; 1019 } 1020 1021 if (mChild && 1022 (mChild->GetParentNode() != mParent || mChild->IsBeingRemoved())) { 1023 return false; 1024 } 1025 if (mOffset.isSome() && mOffset.value() > mParent->Length()) { 1026 return false; 1027 } 1028 return true; 1029 } 1030 1031 [[nodiscard]] bool IsInContentNodeAndValid() const { 1032 return IsInContentNode() && IsSetAndValid(); 1033 } 1034 1035 [[nodiscard]] bool IsInComposedDoc() const { 1036 return IsSet() && mParent->IsInComposedDoc(); 1037 } 1038 1039 [[nodiscard]] bool IsSetAndValidInComposedDoc() const { 1040 return IsInComposedDoc() && IsSetAndValid(); 1041 } 1042 1043 [[nodiscard]] bool IsInContentNodeAndValidInComposedDoc() const { 1044 return IsInContentNode() && IsSetAndValidInComposedDoc(); 1045 } 1046 1047 [[nodiscard]] bool IsInNativeAnonymousSubtreeInTextControl() const { 1048 if (!mParent || !mParent->IsInNativeAnonymousSubtree()) { 1049 return false; 1050 } 1051 nsIContent* maybeTextControl = 1052 mParent->GetClosestNativeAnonymousSubtreeRootParentOrHost(); 1053 return !!maybeTextControl; 1054 } 1055 1056 [[nodiscard]] bool IsStartOfContainer() const { 1057 // If we're referring the first point in the container: 1058 // If mParent is not a container like a text node, mOffset is 0. 1059 // If mChild is initialized and it's first child of mParent. 1060 // If mChild isn't initialized and the offset is 0. 1061 if (NS_WARN_IF(!mParent)) { 1062 return false; 1063 } 1064 if (!mParent->IsContainerNode()) { 1065 return !mOffset.value(); 1066 } 1067 if (mIsChildInitialized) { 1068 if (mParent->GetFirstChild() == mChild) { 1069 NS_WARNING_ASSERTION(!mOffset.isSome() || !mOffset.value(), 1070 "If mOffset was initialized, it should be 0"); 1071 return true; 1072 } 1073 NS_WARNING_ASSERTION(!mOffset.isSome() || mParent->GetChildAt_Deprecated( 1074 mOffset.value()) == mChild, 1075 "mOffset and mChild are mismatched"); 1076 return false; 1077 } 1078 MOZ_ASSERT(mOffset.isSome()); 1079 return !mOffset.value(); 1080 } 1081 1082 [[nodiscard]] bool IsMiddleOfContainer() const { 1083 if (NS_WARN_IF(!mParent)) { 1084 return false; 1085 } 1086 if (mParent->IsText()) { 1087 return *mOffset && *mOffset < mParent->Length(); 1088 } 1089 if (!mParent->HasChildren()) { 1090 return false; 1091 } 1092 if (mIsChildInitialized) { 1093 NS_WARNING_ASSERTION( 1094 mOffset.isNothing() || 1095 (!mChild && *mOffset == mParent->GetChildCount()) || 1096 (mChild && mOffset == mParent->ComputeIndexOf(mChild)), 1097 "mOffset does not match with current offset of mChild"); 1098 return mChild && mChild != mParent->GetFirstChild(); 1099 } 1100 MOZ_ASSERT(mOffset.isSome()); 1101 return *mOffset && *mOffset < mParent->Length(); 1102 } 1103 1104 [[nodiscard]] bool IsEndOfContainer() const { 1105 // If we're referring after the last point of the container: 1106 // If mParent is not a container like text node, mOffset is same as the 1107 // length of the container. 1108 // If mChild is initialized and it's nullptr. 1109 // If mChild isn't initialized and mOffset is same as the length of the 1110 // container. 1111 if (NS_WARN_IF(!mParent)) { 1112 return false; 1113 } 1114 if (!mParent->IsContainerNode()) { 1115 return mOffset.value() == mParent->Length(); 1116 } 1117 if (mIsChildInitialized) { 1118 if (!mChild) { 1119 NS_WARNING_ASSERTION( 1120 !mOffset.isSome() || mOffset.value() == mParent->Length(), 1121 "If mOffset was initialized, it should be length of the container"); 1122 return true; 1123 } 1124 NS_WARNING_ASSERTION(!mOffset.isSome() || mParent->GetChildAt_Deprecated( 1125 mOffset.value()) == mChild, 1126 "mOffset and mChild are mismatched"); 1127 return false; 1128 } 1129 MOZ_ASSERT(mOffset.isSome()); 1130 return mOffset.value() == mParent->Length(); 1131 } 1132 1133 /** 1134 * IsAtLastContent() returns true when it refers last child of the container 1135 * or last character offset of text node. 1136 */ 1137 bool IsAtLastContent() const { 1138 if (NS_WARN_IF(!mParent)) { 1139 return false; 1140 } 1141 if (mParent->IsContainerNode() && mOffset.isSome()) { 1142 return mOffset.value() == mParent->Length() - 1; 1143 } 1144 if (mIsChildInitialized) { 1145 if (mChild && mChild == mParent->GetLastChild()) { 1146 NS_WARNING_ASSERTION( 1147 !mOffset.isSome() || mOffset.value() == mParent->Length() - 1, 1148 "If mOffset was initialized, it should be length - 1 of the " 1149 "container"); 1150 return true; 1151 } 1152 NS_WARNING_ASSERTION(!mOffset.isSome() || mParent->GetChildAt_Deprecated( 1153 mOffset.value()) == mChild, 1154 "mOffset and mChild are mismatched"); 1155 return false; 1156 } 1157 MOZ_ASSERT(mOffset.isSome()); 1158 return mOffset.value() == mParent->Length() - 1; 1159 } 1160 1161 /** 1162 * Return a point in text node if "this" points around a text node. 1163 * EditorDOMPointType can always be EditorDOMPoint or EditorRawDOMPoint, 1164 * but EditorDOMPointInText or EditorRawDOMPointInText is also available 1165 * only when "this type" is one of them. 1166 * If the point is in the anonymous <div> of a TextEditor, use 1167 * TextEditor::FindBetterInsertionPoint() instead. 1168 */ 1169 template <typename EditorDOMPointType> 1170 EditorDOMPointType GetPointInTextNodeIfPointingAroundTextNode() const { 1171 if (NS_WARN_IF(!IsSet()) || !mParent->HasChildren()) { 1172 return To<EditorDOMPointType>(); 1173 } 1174 if (IsStartOfContainer()) { 1175 if (auto* firstTextChild = 1176 dom::Text::FromNode(mParent->GetFirstChild())) { 1177 return EditorDOMPointType(firstTextChild, 0u); 1178 } 1179 return To<EditorDOMPointType>(); 1180 } 1181 if (auto* previousSiblingChild = dom::Text::FromNodeOrNull( 1182 GetPreviousSiblingOfChildAs<dom::Text>())) { 1183 return EditorDOMPointType::AtEndOf(*previousSiblingChild); 1184 } 1185 if (auto* child = dom::Text::FromNodeOrNull(GetChildAs<dom::Text>())) { 1186 return EditorDOMPointType(child, 0u); 1187 } 1188 return To<EditorDOMPointType>(); 1189 } 1190 1191 template <typename A, typename B> 1192 EditorDOMPointBase& operator=(const RangeBoundaryBase<A, B>& aOther) { 1193 mParent = aOther.mParent; 1194 mChild = aOther.mRef ? aOther.mRef->GetNextSibling() 1195 : (aOther.mParent && aOther.mParent->IsContainerNode() 1196 ? aOther.mParent->GetFirstChild() 1197 : nullptr); 1198 mOffset = aOther.mOffset; 1199 mIsChildInitialized = 1200 aOther.mRef || (aOther.mParent && !aOther.mParent->IsContainerNode()) || 1201 (aOther.mOffset.isSome() && !aOther.mOffset.value()); 1202 mInterlinePosition = InterlinePosition::Undefined; 1203 return *this; 1204 } 1205 1206 /** 1207 * If EditorDOMPointType is same as SelfType, return the reference of `this`. 1208 * Otherwise, returns new instance of EditorDOMPointType. 1209 * 1210 * So, don't do this: 1211 * auto newPoint = oldPoint.RefOrTo(); 1212 * newPoint.AdvanceOffset(); 1213 * Then, oldPoint is also modified. Change the `auto` to the type of 1214 * `oldPoint` in this case. 1215 * 1216 * If you always want a new instance, you should use To() instead. 1217 */ 1218 template <typename EditorDOMPointType> 1219 constexpr EditorDOMPointType RefOrTo() const { 1220 if constexpr (std::is_same_v<SelfType, EditorDOMPointType>) { 1221 return *this; 1222 } else { 1223 EditorDOMPointType result; 1224 result.mParent = mParent; 1225 result.mChild = mChild; 1226 result.mOffset = mOffset; 1227 result.mIsChildInitialized = mIsChildInitialized; 1228 result.mInterlinePosition = mInterlinePosition; 1229 return result; 1230 } 1231 } 1232 1233 /** 1234 * Even if EditorDOMPointType is same as SelfType, return a copy of `this`. 1235 */ 1236 template <typename EditorDOMPointType> 1237 constexpr EditorDOMPointType To() const { 1238 EditorDOMPointType result; 1239 result.mParent = mParent; 1240 result.mChild = mChild; 1241 result.mOffset = mOffset; 1242 result.mIsChildInitialized = mIsChildInitialized; 1243 result.mInterlinePosition = mInterlinePosition; 1244 return result; 1245 } 1246 1247 /** 1248 * Don't compare mInterlinePosition. If it's required to check, perhaps, 1249 * another compare operator like `===` should be created. 1250 */ 1251 template <typename A, typename B> 1252 bool operator==(const EditorDOMPointBase<A, B>& aOther) const { 1253 if (mParent != aOther.mParent) { 1254 return false; 1255 } 1256 1257 if (mOffset.isSome() && aOther.mOffset.isSome()) { 1258 // If both mOffset are set, we need to compare both mRef too because 1259 // the relation of mRef and mOffset have already broken by DOM tree 1260 // changes. 1261 if (mOffset != aOther.mOffset) { 1262 return false; 1263 } 1264 if (mChild == aOther.mChild) { 1265 return true; 1266 } 1267 if (NS_WARN_IF(mIsChildInitialized && aOther.mIsChildInitialized)) { 1268 // In this case, relation between mChild and mOffset of one of or both 1269 // of them doesn't match with current DOM tree since the DOM tree might 1270 // have been changed after computing mChild or mOffset. 1271 return false; 1272 } 1273 // If one of mChild hasn't been computed yet, we should compare them only 1274 // with mOffset. Perhaps, we shouldn't copy mChild from non-nullptr one 1275 // to the other since if we copy it here, it may be unexpected behavior 1276 // for some callers. 1277 return true; 1278 } 1279 1280 MOZ_ASSERT(mIsChildInitialized || aOther.mIsChildInitialized); 1281 1282 if (mOffset.isSome() && !mIsChildInitialized && !aOther.mOffset.isSome() && 1283 aOther.mIsChildInitialized) { 1284 // If this has only mOffset and the other has only mChild, this needs to 1285 // compute mChild now. 1286 const_cast<SelfType*>(this)->EnsureChild(); 1287 return mChild == aOther.mChild; 1288 } 1289 1290 if (!mOffset.isSome() && mIsChildInitialized && aOther.mOffset.isSome() && 1291 !aOther.mIsChildInitialized) { 1292 // If this has only mChild and the other has only mOffset, the other needs 1293 // to compute mChild now. 1294 const_cast<EditorDOMPointBase<A, B>&>(aOther).EnsureChild(); 1295 return mChild == aOther.mChild; 1296 } 1297 1298 // If mOffset of one of them hasn't been computed from mChild yet, we should 1299 // compare only with mChild. Perhaps, we shouldn't copy mOffset from being 1300 // some one to not being some one since if we copy it here, it may be 1301 // unexpected behavior for some callers. 1302 return mChild == aOther.mChild; 1303 } 1304 1305 template <typename A, typename B> 1306 bool operator==(const RangeBoundaryBase<A, B>& aOther) const { 1307 // TODO: Optimize this with directly comparing with RangeBoundaryBase 1308 // members. 1309 return *this == SelfType(aOther); 1310 } 1311 1312 template <typename A, typename B> 1313 bool operator!=(const EditorDOMPointBase<A, B>& aOther) const { 1314 return !(*this == aOther); 1315 } 1316 1317 template <typename A, typename B> 1318 bool operator!=(const RangeBoundaryBase<A, B>& aOther) const { 1319 return !(*this == aOther); 1320 } 1321 1322 /** 1323 * This operator should be used if API of other modules take RawRangeBoundary, 1324 * e.g., methods of Selection and nsRange. 1325 */ 1326 operator const RawRangeBoundary() const { return ToRawRangeBoundary(); } 1327 const RawRangeBoundary ToRawRangeBoundary() const { 1328 if (!IsSet() || NS_WARN_IF(!mIsChildInitialized && !mOffset.isSome())) { 1329 return RawRangeBoundary(); 1330 } 1331 if (!mParent->IsContainerNode()) { 1332 MOZ_ASSERT(mOffset.value() <= mParent->Length()); 1333 // If the container is a data node like a text node, we need to create 1334 // RangeBoundaryBase instance only with mOffset because mChild is always 1335 // nullptr. 1336 return RawRangeBoundary(mParent, mOffset.value(), 1337 // Avoid immediately to compute the child node. 1338 RangeBoundaryIsMutationObserved::No); 1339 } 1340 if (mIsChildInitialized && mOffset.isSome()) { 1341 // If we've already set both child and offset, we should create 1342 // RangeBoundary with offset after validation. 1343 #ifdef DEBUG 1344 if (mChild) { 1345 MOZ_ASSERT(mParent == mChild->GetParentNode()); 1346 MOZ_ASSERT(mParent->GetChildAt_Deprecated(mOffset.value()) == mChild); 1347 } else { 1348 MOZ_ASSERT(mParent->Length() == mOffset.value()); 1349 } 1350 #endif // #ifdef DEBUG 1351 return RawRangeBoundary(mParent, mOffset.value(), 1352 // Avoid immediately to compute the child node. 1353 RangeBoundaryIsMutationObserved::No); 1354 } 1355 // Otherwise, we should create RangeBoundaryBase only with available 1356 // information. 1357 if (mOffset.isSome()) { 1358 return RawRangeBoundary(mParent, mOffset.value(), 1359 // Avoid immediately to compute the child node. 1360 RangeBoundaryIsMutationObserved::No); 1361 } 1362 if (mChild) { 1363 return RawRangeBoundary(mParent, mChild->GetPreviousSibling()); 1364 } 1365 return RawRangeBoundary(mParent, mParent->GetLastChild()); 1366 } 1367 1368 already_AddRefed<nsRange> CreateCollapsedRange(ErrorResult& aRv) const { 1369 const RawRangeBoundary boundary = ToRawRangeBoundary(); 1370 RefPtr<nsRange> range = nsRange::Create(boundary, boundary, aRv); 1371 if (MOZ_UNLIKELY(aRv.Failed() || !range)) { 1372 return nullptr; 1373 } 1374 return range.forget(); 1375 } 1376 1377 [[nodiscard]] EditorDOMPointInText GetAsInText() const { 1378 return IsInTextNode() ? EditorDOMPointInText(ContainerAs<dom::Text>(), 1379 Offset(), mInterlinePosition) 1380 : EditorDOMPointInText(); 1381 } 1382 [[nodiscard]] EditorDOMPointInText AsInText() const { 1383 MOZ_ASSERT(IsInTextNode()); 1384 return EditorDOMPointInText(ContainerAs<dom::Text>(), Offset(), 1385 mInterlinePosition); 1386 } 1387 [[nodiscard]] EditorRawDOMPointInText GetAsRawInText() const { 1388 return IsInTextNode() 1389 ? EditorRawDOMPointInText(ContainerAs<dom::Text>(), Offset(), 1390 mInterlinePosition) 1391 : EditorRawDOMPointInText(); 1392 } 1393 [[nodiscard]] EditorRawDOMPointInText AsRawInText() const { 1394 MOZ_ASSERT(IsInTextNode()); 1395 return EditorRawDOMPointInText(ContainerAs<dom::Text>(), Offset(), 1396 mInterlinePosition); 1397 } 1398 1399 template <typename A, typename B> 1400 bool IsBefore(const EditorDOMPointBase<A, B>& aOther) const { 1401 if (!IsSetAndValid() || !aOther.IsSetAndValid()) { 1402 return false; 1403 } 1404 Maybe<int32_t> comp = nsContentUtils::ComparePoints( 1405 ToRawRangeBoundary(), aOther.ToRawRangeBoundary()); 1406 return comp.isSome() && comp.value() == -1; 1407 } 1408 1409 template <typename A, typename B> 1410 bool EqualsOrIsBefore(const EditorDOMPointBase<A, B>& aOther) const { 1411 if (!IsSetAndValid() || !aOther.IsSetAndValid()) { 1412 return false; 1413 } 1414 Maybe<int32_t> comp = nsContentUtils::ComparePoints( 1415 ToRawRangeBoundary(), aOther.ToRawRangeBoundary()); 1416 return comp.isSome() && comp.value() <= 0; 1417 } 1418 1419 friend std::ostream& operator<<(std::ostream& aStream, 1420 const SelfType& aDOMPoint) { 1421 aStream << "{ mParent=" << aDOMPoint.GetContainer(); 1422 if (aDOMPoint.mParent) { 1423 const auto* parentAsText = dom::Text::FromNode(aDOMPoint.mParent); 1424 if (parentAsText && parentAsText->TextDataLength()) { 1425 nsAutoString data; 1426 parentAsText->AppendTextTo(data); 1427 if (data.Length() > 10) { 1428 data.Truncate(10); 1429 } 1430 data.ReplaceSubstring(u"\n", u"\\n"); 1431 data.ReplaceSubstring(u"\r", u"\\r"); 1432 data.ReplaceSubstring(u"\t", u"\\t"); 1433 data.ReplaceSubstring(u"\f", u"\\f"); 1434 data.ReplaceSubstring(u"\u00A0", u" "); 1435 aStream << " (" << *parentAsText << ", (begins with=\"" 1436 << NS_ConvertUTF16toUTF8(data).get() 1437 << "\"), Length()=" << parentAsText->TextDataLength() << ")"; 1438 } else { 1439 aStream << " (" << *aDOMPoint.mParent 1440 << ", Length()=" << aDOMPoint.mParent->Length() << ")"; 1441 } 1442 } 1443 aStream << ", mChild=" << static_cast<nsIContent*>(aDOMPoint.mChild); 1444 if (aDOMPoint.mChild) { 1445 aStream << " (" << *aDOMPoint.mChild << ")"; 1446 } 1447 aStream << ", mOffset=" << aDOMPoint.mOffset << ", mIsChildInitialized=" 1448 << (aDOMPoint.mIsChildInitialized ? "true" : "false") 1449 << ", mInterlinePosition=" << aDOMPoint.mInterlinePosition << " }"; 1450 return aStream; 1451 } 1452 1453 private: 1454 void EnsureChild() { 1455 if (mIsChildInitialized) { 1456 return; 1457 } 1458 if (!mParent) { 1459 MOZ_ASSERT(!mOffset.isSome()); 1460 return; 1461 } 1462 MOZ_ASSERT(mOffset.isSome()); 1463 MOZ_ASSERT(mOffset.value() <= mParent->Length()); 1464 mIsChildInitialized = true; 1465 if (!mParent->IsContainerNode()) { 1466 return; 1467 } 1468 mChild = mParent->GetChildAt_Deprecated(mOffset.value()); 1469 MOZ_ASSERT(mChild || mOffset.value() == mParent->Length()); 1470 } 1471 1472 ParentType mParent = nullptr; 1473 ChildType mChild = nullptr; 1474 1475 Maybe<uint32_t> mOffset; 1476 InterlinePosition mInterlinePosition = InterlinePosition::Undefined; 1477 bool mIsChildInitialized = false; 1478 1479 template <typename PT, typename CT> 1480 friend class EditorDOMPointBase; 1481 1482 friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&, 1483 EditorDOMPoint&, const char*, 1484 uint32_t); 1485 friend void ImplCycleCollectionUnlink(EditorDOMPoint&); 1486 }; 1487 1488 inline void ImplCycleCollectionUnlink(EditorDOMPoint& aField) { 1489 ImplCycleCollectionUnlink(aField.mParent); 1490 ImplCycleCollectionUnlink(aField.mChild); 1491 } 1492 1493 inline void ImplCycleCollectionTraverse( 1494 nsCycleCollectionTraversalCallback& aCallback, EditorDOMPoint& aField, 1495 const char* aName, uint32_t aFlags) { 1496 ImplCycleCollectionTraverse(aCallback, aField.mParent, "mParent", 0); 1497 ImplCycleCollectionTraverse(aCallback, aField.mChild, "mChild", 0); 1498 } 1499 1500 /** 1501 * EditorDOMRangeBase class stores a pair of same EditorDOMPointBase type. 1502 * The instance must be created with valid DOM points and start must be 1503 * before or same as end. 1504 */ 1505 #define NS_INSTANTIATE_EDITOR_DOM_RANGE_METHOD(aResultType, aMethodName, ...) \ 1506 template aResultType EditorDOMRange::aMethodName(__VA_ARGS__); \ 1507 template aResultType EditorRawDOMRange::aMethodName(__VA_ARGS__); \ 1508 template aResultType EditorDOMRangeInTexts::aMethodName(__VA_ARGS__); \ 1509 template aResultType EditorRawDOMRangeInTexts::aMethodName(__VA_ARGS__) 1510 1511 #define NS_INSTANTIATE_EDITOR_DOM_RANGE_CONST_METHOD(aResultType, aMethodName, \ 1512 ...) \ 1513 template aResultType EditorDOMRange::aMethodName(__VA_ARGS__) const; \ 1514 template aResultType EditorRawDOMRange::aMethodName(__VA_ARGS__) const; \ 1515 template aResultType EditorDOMRangeInTexts::aMethodName(__VA_ARGS__) const; \ 1516 template aResultType EditorRawDOMRangeInTexts::aMethodName(__VA_ARGS__) const 1517 template <typename EditorDOMPointType> 1518 class EditorDOMRangeBase final { 1519 using SelfType = EditorDOMRangeBase<EditorDOMPointType>; 1520 1521 public: 1522 using PointType = EditorDOMPointType; 1523 1524 EditorDOMRangeBase() = default; 1525 template <typename PT, typename CT> 1526 explicit EditorDOMRangeBase(const EditorDOMPointBase<PT, CT>& aStart) 1527 : mStart(aStart), mEnd(aStart) { 1528 MOZ_ASSERT(!mStart.IsSet() || mStart.IsSetAndValid()); 1529 } 1530 template <typename StartPointType, typename EndPointType> 1531 explicit EditorDOMRangeBase(const StartPointType& aStart, 1532 const EndPointType& aEnd) 1533 : mStart(aStart.template RefOrTo<PointType>()), 1534 mEnd(aEnd.template RefOrTo<PointType>()) { 1535 MOZ_ASSERT_IF(mStart.IsSet(), mStart.IsSetAndValid()); 1536 MOZ_ASSERT_IF(mEnd.IsSet(), mEnd.IsSetAndValid()); 1537 MOZ_ASSERT_IF(mStart.IsSet() && mEnd.IsSet(), 1538 mStart.EqualsOrIsBefore(mEnd)); 1539 } 1540 template <typename EndPointType> 1541 explicit EditorDOMRangeBase(PointType&& aStart, EndPointType& aEnd) 1542 : mStart(std::forward<PointType>(aStart)), 1543 mEnd(aEnd.template RefOrTo<PointType>()) { 1544 MOZ_ASSERT_IF(mStart.IsSet(), mStart.IsSetAndValid()); 1545 MOZ_ASSERT_IF(mEnd.IsSet(), mEnd.IsSetAndValid()); 1546 MOZ_ASSERT_IF(mStart.IsSet() && mEnd.IsSet(), 1547 mStart.EqualsOrIsBefore(mEnd)); 1548 } 1549 template <typename StartPointType> 1550 explicit EditorDOMRangeBase(StartPointType& aStart, PointType&& aEnd) 1551 : mStart(aStart.template RefOrTo<PointType>()), 1552 mEnd(std::forward<PointType>(aEnd)) { 1553 MOZ_ASSERT_IF(mStart.IsSet(), mStart.IsSetAndValid()); 1554 MOZ_ASSERT_IF(mEnd.IsSet(), mEnd.IsSetAndValid()); 1555 MOZ_ASSERT_IF(mStart.IsSet() && mEnd.IsSet(), 1556 mStart.EqualsOrIsBefore(mEnd)); 1557 } 1558 explicit EditorDOMRangeBase(PointType&& aStart, PointType&& aEnd) 1559 : mStart(std::forward<PointType>(aStart)), 1560 mEnd(std::forward<PointType>(aEnd)) { 1561 MOZ_ASSERT_IF(mStart.IsSet(), mStart.IsSetAndValid()); 1562 MOZ_ASSERT_IF(mEnd.IsSet(), mEnd.IsSetAndValid()); 1563 MOZ_ASSERT_IF(mStart.IsSet() && mEnd.IsSet(), 1564 mStart.EqualsOrIsBefore(mEnd)); 1565 } 1566 template <typename OtherPointType> 1567 explicit EditorDOMRangeBase(const EditorDOMRangeBase<OtherPointType>& aOther) 1568 : mStart(aOther.StartRef().template RefOrTo<PointType>()), 1569 mEnd(aOther.EndRef().template RefOrTo<PointType>()) { 1570 MOZ_ASSERT_IF(mStart.IsSet(), mStart.IsSetAndValid()); 1571 MOZ_ASSERT_IF(mEnd.IsSet(), mEnd.IsSetAndValid()); 1572 MOZ_ASSERT(mStart.IsSet() == mEnd.IsSet()); 1573 } 1574 explicit EditorDOMRangeBase(const dom::AbstractRange& aRange) 1575 : mStart(aRange.StartRef()), mEnd(aRange.EndRef()) { 1576 MOZ_ASSERT_IF(mStart.IsSet(), mStart.IsSetAndValid()); 1577 MOZ_ASSERT_IF(mEnd.IsSet(), mEnd.IsSetAndValid()); 1578 MOZ_ASSERT_IF(mStart.IsSet() && mEnd.IsSet(), 1579 mStart.EqualsOrIsBefore(mEnd)); 1580 } 1581 1582 template <typename MaybeOtherPointType> 1583 void SetStart(const MaybeOtherPointType& aStart) { 1584 mStart = aStart.template RefOrTo<PointType>(); 1585 } 1586 void SetStart(PointType&& aStart) { mStart = std::move(aStart); } 1587 template <typename MaybeOtherPointType> 1588 void SetEnd(const MaybeOtherPointType& aEnd) { 1589 mEnd = aEnd.template RefOrTo<PointType>(); 1590 } 1591 void SetEnd(PointType&& aEnd) { mEnd = std::move(aEnd); } 1592 template <typename StartPointType, typename EndPointType> 1593 void SetStartAndEnd(const StartPointType& aStart, const EndPointType& aEnd) { 1594 MOZ_ASSERT_IF(aStart.IsSet() && aEnd.IsSet(), 1595 aStart.EqualsOrIsBefore(aEnd)); 1596 mStart = aStart.template RefOrTo<PointType>(); 1597 mEnd = aEnd.template RefOrTo<PointType>(); 1598 } 1599 template <typename StartPointType> 1600 void SetStartAndEnd(const StartPointType& aStart, PointType&& aEnd) { 1601 MOZ_ASSERT_IF(aStart.IsSet() && aEnd.IsSet(), 1602 aStart.EqualsOrIsBefore(aEnd)); 1603 mStart = aStart.template RefOrTo<PointType>(); 1604 mEnd = std::move(aEnd); 1605 } 1606 template <typename EndPointType> 1607 void SetStartAndEnd(PointType&& aStart, const EndPointType& aEnd) { 1608 MOZ_ASSERT_IF(aStart.IsSet() && aEnd.IsSet(), 1609 aStart.EqualsOrIsBefore(aEnd)); 1610 mStart = std::move(aStart); 1611 mEnd = aEnd.template RefOrTo<PointType>(); 1612 } 1613 void SetStartAndEnd(PointType&& aStart, PointType&& aEnd) { 1614 MOZ_ASSERT_IF(aStart.IsSet() && aEnd.IsSet(), 1615 aStart.EqualsOrIsBefore(aEnd)); 1616 mStart = std::move(aStart); 1617 mEnd = std::move(aEnd); 1618 } 1619 template <typename PT, typename CT> 1620 void MergeWith(const EditorDOMPointBase<PT, CT>& aPoint) { 1621 MOZ_ASSERT(aPoint.IsSet()); 1622 if (!IsPositioned()) { 1623 SetStartAndEnd(aPoint, aPoint); 1624 return; 1625 } 1626 MOZ_ASSERT(nsContentUtils::GetClosestCommonInclusiveAncestor( 1627 GetClosestCommonInclusiveAncestor(), aPoint.GetContainer())); 1628 if (mEnd.EqualsOrIsBefore(aPoint)) { 1629 SetEnd(aPoint); 1630 return; 1631 } 1632 if (aPoint.IsBefore(mStart)) { 1633 SetStart(aPoint); 1634 return; 1635 } 1636 } 1637 void MergeWith(PointType&& aPoint) { 1638 MOZ_ASSERT(aPoint.IsSet()); 1639 if (!IsPositioned()) { 1640 SetStartAndEnd(aPoint, aPoint); 1641 return; 1642 } 1643 MOZ_ASSERT(GetClosestCommonInclusiveAncestor()); 1644 MOZ_ASSERT(nsContentUtils::GetClosestCommonInclusiveAncestor( 1645 GetClosestCommonInclusiveAncestor(), aPoint.GetContainer())); 1646 if (mEnd.EqualsOrIsBefore(aPoint)) { 1647 SetEnd(std::move(aPoint)); 1648 return; 1649 } 1650 if (aPoint.IsBefore(mStart)) { 1651 SetStart(std::move(aPoint)); 1652 return; 1653 } 1654 } 1655 template <typename PT, typename CT> 1656 void MergeWith(const EditorDOMRangeBase<EditorDOMPointBase<PT, CT>>& aRange) { 1657 MOZ_ASSERT(aRange.IsPositioned()); 1658 MOZ_ASSERT(aRange.GetClosestCommonInclusiveAncestor()); 1659 if (!IsPositioned()) { 1660 SetStartAndEnd(aRange.mStart, aRange.mEnd); 1661 return; 1662 } 1663 MOZ_ASSERT(GetClosestCommonInclusiveAncestor()); 1664 MOZ_ASSERT(nsContentUtils::GetClosestCommonInclusiveAncestor( 1665 GetClosestCommonInclusiveAncestor(), 1666 aRange.GetClosestCommonInclusiveAncestor())); 1667 if (mEnd.IsBefore(aRange.mEnd)) { 1668 SetEnd(aRange.mEnd); 1669 } 1670 if (aRange.mStart.IsBefore(mStart)) { 1671 SetStart(aRange.mStart); 1672 } 1673 } 1674 void MergeWith(SelfType&& aRange) { 1675 MOZ_ASSERT(aRange.IsPositioned()); 1676 MOZ_ASSERT(aRange.GetClosestCommonInclusiveAncestor()); 1677 if (!IsPositioned()) { 1678 SetStartAndEnd(std::move(aRange.mStart), std::move(aRange.mEnd)); 1679 return; 1680 } 1681 MOZ_ASSERT(GetClosestCommonInclusiveAncestor()); 1682 MOZ_ASSERT(nsContentUtils::GetClosestCommonInclusiveAncestor( 1683 GetClosestCommonInclusiveAncestor(), 1684 aRange.GetClosestCommonInclusiveAncestor())); 1685 if (mEnd.IsBefore(aRange.mEnd)) { 1686 SetEnd(std::move(aRange.mEnd)); 1687 } 1688 if (aRange.mStart.IsBefore(mStart)) { 1689 SetStart(std::move(aRange.mStart)); 1690 } 1691 aRange.Clear(); 1692 } 1693 void Clear() { 1694 mStart.Clear(); 1695 mEnd.Clear(); 1696 } 1697 1698 const PointType& StartRef() const { return mStart; } 1699 const PointType& EndRef() const { return mEnd; } 1700 1701 bool Collapsed() const { 1702 MOZ_ASSERT(IsPositioned()); 1703 return mStart == mEnd; 1704 } 1705 bool IsPositioned() const { return mStart.IsSet() && mEnd.IsSet(); } 1706 bool IsPositionedAndValid() const { 1707 return mStart.IsSetAndValid() && mEnd.IsSetAndValid() && 1708 mStart.EqualsOrIsBefore(mEnd); 1709 } 1710 bool IsPositionedAndValidInComposedDoc() const { 1711 return IsPositionedAndValid() && mStart.GetContainer()->IsInComposedDoc(); 1712 } 1713 template <typename OtherPointType> 1714 MOZ_NEVER_INLINE_DEBUG bool Contains(const OtherPointType& aPoint) const { 1715 MOZ_ASSERT(aPoint.IsSetAndValid()); 1716 return IsPositioned() && aPoint.IsSet() && 1717 mStart.EqualsOrIsBefore(aPoint) && aPoint.IsBefore(mEnd); 1718 } 1719 [[nodiscard]] nsINode* GetClosestCommonInclusiveAncestor() const; 1720 bool InSameContainer() const { 1721 MOZ_ASSERT(IsPositioned()); 1722 return IsPositioned() && mStart.GetContainer() == mEnd.GetContainer(); 1723 } 1724 bool InAdjacentSiblings() const { 1725 MOZ_ASSERT(IsPositioned()); 1726 return IsPositioned() && 1727 mStart.GetContainer()->GetNextSibling() == mEnd.GetContainer(); 1728 } 1729 bool IsInContentNodes() const { 1730 MOZ_ASSERT(IsPositioned()); 1731 return IsPositioned() && mStart.IsInContentNode() && mEnd.IsInContentNode(); 1732 } 1733 bool IsInTextNodes() const { 1734 MOZ_ASSERT(IsPositioned()); 1735 return IsPositioned() && mStart.IsInTextNode() && mEnd.IsInTextNode(); 1736 } 1737 template <typename OtherRangeType> 1738 bool operator==(const OtherRangeType& aOther) const { 1739 return (!IsPositioned() && !aOther.IsPositioned()) || 1740 (mStart == aOther.StartRef() && mEnd == aOther.EndRef()); 1741 } 1742 template <typename OtherRangeType> 1743 bool operator!=(const OtherRangeType& aOther) const { 1744 return !(*this == aOther); 1745 } 1746 1747 EditorDOMRangeInTexts GetAsInTexts() const { 1748 return IsInTextNodes() 1749 ? EditorDOMRangeInTexts(mStart.AsInText(), mEnd.AsInText()) 1750 : EditorDOMRangeInTexts(); 1751 } 1752 MOZ_NEVER_INLINE_DEBUG EditorDOMRangeInTexts AsInTexts() const { 1753 MOZ_ASSERT(IsInTextNodes()); 1754 return EditorDOMRangeInTexts(mStart.AsInText(), mEnd.AsInText()); 1755 } 1756 1757 bool EnsureNotInNativeAnonymousSubtree() { 1758 if (mStart.IsInNativeAnonymousSubtree()) { 1759 nsIContent* parent = nullptr; 1760 for (parent = mStart.template ContainerAs<nsIContent>() 1761 ->GetClosestNativeAnonymousSubtreeRootParentOrHost(); 1762 parent && parent->IsInNativeAnonymousSubtree(); 1763 parent = 1764 parent->GetClosestNativeAnonymousSubtreeRootParentOrHost()) { 1765 } 1766 if (MOZ_UNLIKELY(!parent)) { 1767 return false; 1768 } 1769 mStart.Set(parent); 1770 } 1771 if (mEnd.IsInNativeAnonymousSubtree()) { 1772 nsIContent* parent = nullptr; 1773 for (parent = mEnd.template ContainerAs<nsIContent>() 1774 ->GetClosestNativeAnonymousSubtreeRootParentOrHost(); 1775 parent && parent->IsInNativeAnonymousSubtree(); 1776 parent = 1777 parent->GetClosestNativeAnonymousSubtreeRootParentOrHost()) { 1778 } 1779 if (MOZ_UNLIKELY(!parent)) { 1780 return false; 1781 } 1782 mEnd.SetAfter(parent); 1783 } 1784 return true; 1785 } 1786 1787 already_AddRefed<nsRange> CreateRange(ErrorResult& aRv) const { 1788 RefPtr<nsRange> range = nsRange::Create(mStart.ToRawRangeBoundary(), 1789 mEnd.ToRawRangeBoundary(), aRv); 1790 if (MOZ_UNLIKELY(aRv.Failed() || !range)) { 1791 return nullptr; 1792 } 1793 return range.forget(); 1794 } 1795 nsresult SetToRange(nsRange& aRange) const { 1796 return aRange.SetStartAndEnd(mStart.ToRawRangeBoundary(), 1797 mEnd.ToRawRangeBoundary()); 1798 } 1799 1800 friend std::ostream& operator<<(std::ostream& aStream, 1801 const SelfType& aRange) { 1802 if (aRange.Collapsed()) { 1803 aStream << "{ mStart=mEnd=" << aRange.mStart << " }"; 1804 } else { 1805 aStream << "{ mStart=" << aRange.mStart << ", mEnd=" << aRange.mEnd 1806 << " }"; 1807 } 1808 return aStream; 1809 } 1810 1811 private: 1812 EditorDOMPointType mStart; 1813 EditorDOMPointType mEnd; 1814 1815 friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&, 1816 EditorDOMRange&, const char*, 1817 uint32_t); 1818 friend void ImplCycleCollectionUnlink(EditorDOMRange&); 1819 }; 1820 1821 inline void ImplCycleCollectionUnlink(EditorDOMRange& aField) { 1822 ImplCycleCollectionUnlink(aField.mStart); 1823 ImplCycleCollectionUnlink(aField.mEnd); 1824 } 1825 1826 inline void ImplCycleCollectionTraverse( 1827 nsCycleCollectionTraversalCallback& aCallback, EditorDOMRange& aField, 1828 const char* aName, uint32_t aFlags) { 1829 ImplCycleCollectionTraverse(aCallback, aField.mStart, "mStart", 0); 1830 ImplCycleCollectionTraverse(aCallback, aField.mEnd, "mEnd", 0); 1831 } 1832 1833 /** 1834 * AutoEditorDOMPointOffsetInvalidator is useful if DOM tree will be changed 1835 * when EditorDOMPoint instance is available and keeps referring same child 1836 * node. 1837 * 1838 * This class automatically guarantees that given EditorDOMPoint instance 1839 * stores the child node and invalidates its offset when the instance is 1840 * destroyed. Additionally, users of this class can invalidate the offset 1841 * manually when they need. 1842 */ 1843 class MOZ_STACK_CLASS AutoEditorDOMPointOffsetInvalidator final { 1844 public: 1845 AutoEditorDOMPointOffsetInvalidator() = delete; 1846 AutoEditorDOMPointOffsetInvalidator( 1847 const AutoEditorDOMPointOffsetInvalidator&) = delete; 1848 AutoEditorDOMPointOffsetInvalidator(AutoEditorDOMPointOffsetInvalidator&&) = 1849 delete; 1850 const AutoEditorDOMPointOffsetInvalidator& operator=( 1851 const AutoEditorDOMPointOffsetInvalidator&) = delete; 1852 explicit AutoEditorDOMPointOffsetInvalidator(EditorDOMPoint& aPoint) 1853 : mPoint(aPoint), mCanceled(false) { 1854 MOZ_ASSERT(aPoint.IsSetAndValid()); 1855 MOZ_ASSERT(mPoint.CanContainerHaveChildren()); 1856 mChild = mPoint.GetChild(); 1857 } 1858 1859 ~AutoEditorDOMPointOffsetInvalidator() { 1860 if (!mCanceled) { 1861 InvalidateOffset(); 1862 } 1863 } 1864 1865 /** 1866 * Manually, invalidate offset of the given point. 1867 */ 1868 void InvalidateOffset() { 1869 if (mChild) { 1870 mPoint.Set(mChild); 1871 } else { 1872 // If the point referred after the last child, let's keep referring 1873 // after current last node of the old container. 1874 mPoint.SetToEndOf(mPoint.GetContainer()); 1875 } 1876 } 1877 1878 /** 1879 * After calling Cancel(), mPoint won't be modified by the destructor. 1880 */ 1881 void Cancel() { mCanceled = true; } 1882 1883 private: 1884 EditorDOMPoint& mPoint; 1885 // Needs to store child node by ourselves because EditorDOMPoint stores 1886 // child node with mRef which is previous sibling of current child node. 1887 // Therefore, we cannot keep referring it if it's first child. 1888 nsCOMPtr<nsIContent> mChild; 1889 1890 bool mCanceled; 1891 }; 1892 1893 class MOZ_STACK_CLASS AutoEditorDOMRangeOffsetsInvalidator final { 1894 public: 1895 explicit AutoEditorDOMRangeOffsetsInvalidator(EditorDOMRange& aRange) 1896 : mStartInvalidator(const_cast<EditorDOMPoint&>(aRange.StartRef())), 1897 mEndInvalidator(const_cast<EditorDOMPoint&>(aRange.EndRef())) {} 1898 1899 void InvalidateOffsets() { 1900 mStartInvalidator.InvalidateOffset(); 1901 mEndInvalidator.InvalidateOffset(); 1902 } 1903 1904 void Cancel() { 1905 mStartInvalidator.Cancel(); 1906 mEndInvalidator.Cancel(); 1907 } 1908 1909 private: 1910 AutoEditorDOMPointOffsetInvalidator mStartInvalidator; 1911 AutoEditorDOMPointOffsetInvalidator mEndInvalidator; 1912 }; 1913 1914 /** 1915 * AutoEditorDOMPointChildInvalidator is useful if DOM tree will be changed 1916 * when EditorDOMPoint instance is available and keeps referring same container 1917 * and offset in it. 1918 * 1919 * This class automatically guarantees that given EditorDOMPoint instance 1920 * stores offset and invalidates its child node when the instance is destroyed. 1921 * Additionally, users of this class can invalidate the child manually when 1922 * they need. 1923 */ 1924 class MOZ_STACK_CLASS AutoEditorDOMPointChildInvalidator final { 1925 public: 1926 AutoEditorDOMPointChildInvalidator() = delete; 1927 AutoEditorDOMPointChildInvalidator( 1928 const AutoEditorDOMPointChildInvalidator&) = delete; 1929 AutoEditorDOMPointChildInvalidator(AutoEditorDOMPointChildInvalidator&&) = 1930 delete; 1931 const AutoEditorDOMPointChildInvalidator& operator=( 1932 const AutoEditorDOMPointChildInvalidator&) = delete; 1933 explicit AutoEditorDOMPointChildInvalidator(EditorDOMPoint& aPoint) 1934 : mPoint(aPoint), mCanceled(false) { 1935 MOZ_ASSERT(aPoint.IsSetAndValid()); 1936 (void)mPoint.Offset(); 1937 } 1938 1939 ~AutoEditorDOMPointChildInvalidator() { 1940 if (!mCanceled) { 1941 InvalidateChild(); 1942 } 1943 } 1944 1945 /** 1946 * Manually, invalidate child of the given point. 1947 */ 1948 void InvalidateChild() { mPoint.Set(mPoint.GetContainer(), mPoint.Offset()); } 1949 1950 /** 1951 * After calling Cancel(), mPoint won't be modified by the destructor. 1952 */ 1953 void Cancel() { mCanceled = true; } 1954 1955 private: 1956 EditorDOMPoint& mPoint; 1957 1958 bool mCanceled; 1959 }; 1960 1961 class MOZ_STACK_CLASS AutoEditorDOMRangeChildrenInvalidator final { 1962 public: 1963 explicit AutoEditorDOMRangeChildrenInvalidator(EditorDOMRange& aRange) 1964 : mStartInvalidator(const_cast<EditorDOMPoint&>(aRange.StartRef())), 1965 mEndInvalidator(const_cast<EditorDOMPoint&>(aRange.EndRef())) {} 1966 1967 void InvalidateChildren() { 1968 mStartInvalidator.InvalidateChild(); 1969 mEndInvalidator.InvalidateChild(); 1970 } 1971 1972 void Cancel() { 1973 mStartInvalidator.Cancel(); 1974 mEndInvalidator.Cancel(); 1975 } 1976 1977 private: 1978 AutoEditorDOMPointChildInvalidator mStartInvalidator; 1979 AutoEditorDOMPointChildInvalidator mEndInvalidator; 1980 }; 1981 1982 } // namespace mozilla 1983 1984 #endif // #ifndef mozilla_EditorDOMPoint_h