SelectionState.h (26672B)
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_SelectionState_h 7 #define mozilla_SelectionState_h 8 9 #include "mozilla/EditorDOMPoint.h" 10 #include "mozilla/EditorForwards.h" 11 #include "mozilla/Maybe.h" 12 #include "mozilla/OwningNonNull.h" 13 #include "mozilla/dom/Document.h" 14 #include "nsCOMPtr.h" 15 #include "nsDirection.h" 16 #include "nsINode.h" 17 #include "nsRange.h" 18 #include "nsTArray.h" 19 #include "nscore.h" 20 21 class nsCycleCollectionTraversalCallback; 22 class nsRange; 23 namespace mozilla { 24 namespace dom { 25 class Element; 26 class Selection; 27 class Text; 28 } // namespace dom 29 30 /** 31 * A helper struct for saving/setting ranges. 32 */ 33 struct RangeItem final { 34 RangeItem() : mStartOffset(0), mEndOffset(0) {} 35 36 private: 37 // Private destructor, to discourage deletion outside of Release(): 38 ~RangeItem() = default; 39 40 public: 41 void StoreRange(const nsRange& aRange); 42 void StoreRange(const EditorRawDOMPoint& aStartPoint, 43 const EditorRawDOMPoint& aEndPoint) { 44 MOZ_ASSERT(aStartPoint.IsSet()); 45 MOZ_ASSERT(aEndPoint.IsSet()); 46 mStartContainer = aStartPoint.GetContainer(); 47 mStartOffset = aStartPoint.Offset(); 48 mEndContainer = aEndPoint.GetContainer(); 49 mEndOffset = aEndPoint.Offset(); 50 } 51 void Clear() { 52 mStartContainer = mEndContainer = nullptr; 53 mStartOffset = mEndOffset = 0; 54 } 55 already_AddRefed<nsRange> GetRange() const; 56 57 // Same as the API of dom::AbstractRange 58 [[nodiscard]] nsINode* GetRoot() const; 59 [[nodiscard]] bool Collapsed() const { 60 return mStartContainer == mEndContainer && mStartOffset == mEndOffset; 61 } 62 [[nodiscard]] bool IsPositioned() const { 63 return mStartContainer && mEndContainer; 64 } 65 [[nodiscard]] bool Equals(const RangeItem& aOther) const { 66 return mStartContainer == aOther.mStartContainer && 67 mEndContainer == aOther.mEndContainer && 68 mStartOffset == aOther.mStartOffset && 69 mEndOffset == aOther.mEndOffset; 70 } 71 template <typename EditorDOMPointType = EditorDOMPoint> 72 EditorDOMPointType StartPoint() const { 73 return EditorDOMPointType(mStartContainer, mStartOffset); 74 } 75 template <typename EditorDOMPointType = EditorDOMPoint> 76 EditorDOMPointType EndPoint() const { 77 return EditorDOMPointType(mEndContainer, mEndOffset); 78 } 79 80 NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(RangeItem) 81 NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(RangeItem) 82 83 nsCOMPtr<nsINode> mStartContainer; 84 nsCOMPtr<nsINode> mEndContainer; 85 uint32_t mStartOffset; 86 uint32_t mEndOffset; 87 }; 88 89 /** 90 * mozilla::SelectionState 91 * 92 * Class for recording selection info. Stores selection as collection of 93 * { {startnode, startoffset} , {endnode, endoffset} } tuples. Can't store 94 * ranges since dom gravity will possibly change the ranges. 95 */ 96 97 class SelectionState final { 98 public: 99 SelectionState() = default; 100 explicit SelectionState(const AutoClonedSelectionRangeArray& aRanges); 101 102 /** 103 * Same as the API as dom::Selection 104 */ 105 [[nodiscard]] bool IsCollapsed() const { 106 if (mArray.Length() != 1) { 107 return false; 108 } 109 return mArray[0]->Collapsed(); 110 } 111 112 void RemoveAllRanges() { 113 mArray.Clear(); 114 mDirection = eDirNext; 115 } 116 117 [[nodiscard]] uint32_t RangeCount() const { return mArray.Length(); } 118 119 /** 120 * Saving all ranges of aSelection. 121 */ 122 void SaveSelection(dom::Selection& aSelection); 123 124 /** 125 * Setting aSelection to have all ranges stored by this instance. 126 */ 127 MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult 128 RestoreSelection(dom::Selection& aSelection); 129 130 /** 131 * Setting aRanges to have all ranges stored by this instance. 132 */ 133 void ApplyTo(AutoClonedSelectionRangeArray& aRanges); 134 135 /** 136 * HasOnlyCollapsedRange() returns true only when there is a positioned range 137 * which is collapsed. I.e., the selection represents a caret point. 138 */ 139 [[nodiscard]] bool HasOnlyCollapsedRange() const { 140 if (mArray.Length() != 1) { 141 return false; 142 } 143 if (!mArray[0]->IsPositioned() || !mArray[0]->Collapsed()) { 144 return false; 145 } 146 return true; 147 } 148 149 /** 150 * Equals() returns true only when there are same number of ranges and 151 * all their containers and offsets are exactly same. This won't check 152 * the validity of each range with the current DOM tree. 153 */ 154 [[nodiscard]] bool Equals(const SelectionState& aOther) const; 155 156 /** 157 * Returns common root node of all ranges' start and end containers. 158 * Some of them have different root nodes, this returns nullptr. 159 */ 160 [[nodiscard]] nsINode* GetCommonRootNode() const { 161 nsINode* rootNode = nullptr; 162 for (const RefPtr<RangeItem>& rangeItem : mArray) { 163 nsINode* newRootNode = rangeItem->GetRoot(); 164 if (!newRootNode || (rootNode && rootNode != newRootNode)) { 165 return nullptr; 166 } 167 rootNode = newRootNode; 168 } 169 return rootNode; 170 } 171 172 private: 173 CopyableAutoTArray<RefPtr<RangeItem>, 1> mArray; 174 nsDirection mDirection = eDirNext; 175 176 friend class RangeUpdater; 177 friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&, 178 SelectionState&, const char*, 179 uint32_t); 180 friend void ImplCycleCollectionUnlink(SelectionState&); 181 }; 182 183 inline void ImplCycleCollectionTraverse( 184 nsCycleCollectionTraversalCallback& aCallback, SelectionState& aField, 185 const char* aName, uint32_t aFlags = 0) { 186 ImplCycleCollectionTraverse(aCallback, aField.mArray, aName, aFlags); 187 } 188 189 inline void ImplCycleCollectionUnlink(SelectionState& aField) { 190 ImplCycleCollectionUnlink(aField.mArray); 191 } 192 193 class MOZ_STACK_CLASS RangeUpdater final { 194 public: 195 RangeUpdater(); 196 197 void RegisterRangeItem(RangeItem& aRangeItem); 198 void DropRangeItem(RangeItem& aRangeItem); 199 void RegisterSelectionState(SelectionState& aSelectionState); 200 void DropSelectionState(SelectionState& aSelectionState); 201 202 // editor selection gravity routines. Note that we can't always depend on 203 // DOM Range gravity to do what we want to the "real" selection. For 204 // instance, if you move a node, that corresponds to deleting it and 205 // reinserting it. DOM Range gravity will promote the selection out of the 206 // node on deletion, which is not what you want if you know you are 207 // reinserting it. 208 template <typename PT, typename CT> 209 nsresult SelAdjCreateNode(const EditorDOMPointBase<PT, CT>& aPoint); 210 template <typename PT, typename CT> 211 nsresult SelAdjInsertNode(const EditorDOMPointBase<PT, CT>& aPoint); 212 void SelAdjDeleteNode(nsINode& aNode); 213 214 /** 215 * SelAdjSplitNode() is called immediately after spliting aOriginalNode 216 * and inserted aNewContent into the DOM tree. 217 * 218 * @param aOriginalContent The node which was split. 219 * @param aSplitOffset The old offset in aOriginalContent at splitting 220 * it. 221 * @param aNewContent The new content node which was inserted into 222 * the DOM tree. 223 */ 224 nsresult SelAdjSplitNode(nsIContent& aOriginalContent, uint32_t aSplitOffset, 225 nsIContent& aNewContent); 226 227 /** 228 * SelAdjJoinNodes() is called immediately after joining aRemovedContent and 229 * the container of aStartOfRightContent. 230 * 231 * @param aStartOfRightContent The container is joined content node which 232 * now has all children or text data which were 233 * in aRemovedContent. And this points where 234 * the joined position. 235 * @param aRemovedContent The removed content. 236 * @param aOldPointAtRightContent The point where the right content node was 237 * before joining them. The offset must have 238 * been initialized before the joining. 239 */ 240 nsresult SelAdjJoinNodes(const EditorRawDOMPoint& aStartOfRightContent, 241 const nsIContent& aRemovedContent, 242 const EditorDOMPoint& aOldPointAtRightContent); 243 void SelAdjInsertText(const dom::Text& aTextNode, uint32_t aOffset, 244 uint32_t aInsertedLength); 245 void SelAdjDeleteText(const dom::Text& aTextNode, uint32_t aOffset, 246 uint32_t aDeletedLength); 247 void SelAdjReplaceText(const dom::Text& aTextNode, uint32_t aOffset, 248 uint32_t aReplacedLength, uint32_t aInsertedLength); 249 // the following gravity routines need will/did sandwiches, because the other 250 // gravity routines will be called inside of these sandwiches, but should be 251 // ignored. 252 void WillReplaceContainer() { 253 // XXX Isn't this possible with mutation event listener? 254 NS_WARNING_ASSERTION(!mLocked, "Has already been locked"); 255 mLocked = true; 256 } 257 void DidReplaceContainer(const dom::Element& aRemovedElement, 258 dom::Element& aInsertedElement); 259 void WillRemoveContainer() { 260 // XXX Isn't this possible with mutation event listener? 261 NS_WARNING_ASSERTION(!mLocked, "Has already been locked"); 262 mLocked = true; 263 } 264 void DidRemoveContainer(const dom::Element& aRemovedElement, 265 nsINode& aRemovedElementContainerNode, 266 uint32_t aOldOffsetOfRemovedElement, 267 uint32_t aOldChildCountOfRemovedElement); 268 void WillInsertContainer() { 269 // XXX Isn't this possible with mutation event listener? 270 NS_WARNING_ASSERTION(!mLocked, "Has already been locked"); 271 mLocked = true; 272 } 273 void DidInsertContainer() { 274 NS_WARNING_ASSERTION(mLocked, "Not locked"); 275 mLocked = false; 276 } 277 template <typename PT, typename CT> 278 struct SimpleEditorDOMPointBase { 279 SimpleEditorDOMPointBase() = default; 280 SimpleEditorDOMPointBase(const nsINode* aContainer, 281 const nsIContent* aChild, uint32_t aOffset) 282 : mContainer(const_cast<nsINode*>(aContainer)), 283 mChild(const_cast<nsIContent*>(aChild)), 284 mOffset(Some(aOffset)) {} 285 SimpleEditorDOMPointBase(const nsIContent* aChild, uint32_t aOffset) 286 : mContainer(aChild->GetParentNode()), 287 mChild(const_cast<nsIContent*>(aChild)), 288 mOffset(Some(aOffset)) {} 289 SimpleEditorDOMPointBase(const nsINode* aContainer, 290 const nsIContent* aChild) 291 : mContainer(const_cast<nsINode*>(aContainer)), 292 mChild(const_cast<nsIContent*>(aChild)) {} 293 explicit SimpleEditorDOMPointBase(const nsIContent* aChild) 294 : mContainer(aChild->GetParentNode()), 295 mChild(const_cast<nsIContent*>(aChild)) {} 296 297 uint32_t Offset() const { 298 if (!mOffset && mContainer) { 299 mOffset = mContainer->ComputeIndexOf(mChild); 300 } 301 return mOffset.valueOr(0); 302 } 303 nsIContent* GetNextSiblingOfChild() const { 304 return mChild->GetNextSibling(); 305 } 306 PT mContainer; 307 CT mChild; 308 mutable Maybe<uint32_t> mOffset; 309 }; 310 using SimpleEditorDOMPoint = 311 SimpleEditorDOMPointBase<nsCOMPtr<nsINode>, nsCOMPtr<nsIContent>>; 312 using SimpleEditorRawDOMPoint = 313 SimpleEditorDOMPointBase<nsINode*, nsIContent*>; 314 void DidMoveNodes(const nsTArray<SimpleEditorDOMPoint>& aOldPoints, 315 const SimpleEditorDOMPoint& aExpectedDestination, 316 const nsTArray<SimpleEditorDOMPoint>& aNewPoints); 317 318 private: 319 // TODO: A lot of loop in these methods check whether each item `nullptr` or 320 // not. We should make it not nullable later. 321 nsTArray<RefPtr<RangeItem>> mArray; 322 bool mLocked; 323 }; 324 325 enum class StopTracking : bool { No, Yes }; 326 327 /** 328 * Helper class for using SelectionState. Stack based class for doing 329 * preservation of dom points across editor actions. 330 */ 331 332 class MOZ_STACK_CLASS AutoTrackDOMPoint final { 333 public: 334 AutoTrackDOMPoint() = delete; 335 336 AutoTrackDOMPoint(RangeUpdater& aRangeUpdater, CaretPoint* aCaretPoint); 337 338 AutoTrackDOMPoint(RangeUpdater& aRangeUpdater, nsCOMPtr<nsINode>* aNode, 339 uint32_t* aOffset) 340 : mRangeUpdater(aRangeUpdater), 341 mNode(aNode), 342 mOffset(aOffset), 343 mRangeItem(do_AddRef(new RangeItem())), 344 mWasConnected(aNode && (*aNode)->IsInComposedDoc()) { 345 mRangeItem->mStartContainer = *mNode; 346 mRangeItem->mEndContainer = *mNode; 347 mRangeItem->mStartOffset = *mOffset; 348 mRangeItem->mEndOffset = *mOffset; 349 mDocument = (*mNode)->OwnerDoc(); 350 mRangeUpdater.RegisterRangeItem(mRangeItem); 351 } 352 353 AutoTrackDOMPoint(RangeUpdater& aRangeUpdater, EditorDOMPoint* aPoint) 354 : mRangeUpdater(aRangeUpdater), 355 mNode(nullptr), 356 mOffset(nullptr), 357 mPoint(Some(aPoint->IsSet() ? aPoint : nullptr)), 358 mRangeItem(do_AddRef(new RangeItem())), 359 mWasConnected(aPoint && aPoint->IsInComposedDoc()) { 360 if (!aPoint->IsSet()) { 361 mIsTracking = false; 362 return; // Nothing should be tracked. 363 } 364 mRangeItem->mStartContainer = aPoint->GetContainer(); 365 mRangeItem->mEndContainer = aPoint->GetContainer(); 366 mRangeItem->mStartOffset = aPoint->Offset(); 367 mRangeItem->mEndOffset = aPoint->Offset(); 368 mDocument = aPoint->GetContainer()->OwnerDoc(); 369 mRangeUpdater.RegisterRangeItem(mRangeItem); 370 } 371 372 ~AutoTrackDOMPoint() { FlushAndStopTracking(); } 373 374 void Flush(StopTracking aStopTracking) { 375 if (!mIsTracking) { 376 return; 377 } 378 if (static_cast<bool>(aStopTracking)) { 379 mIsTracking = false; 380 } 381 if (mPoint.isSome()) { 382 mRangeUpdater.DropRangeItem(mRangeItem); 383 // Setting `mPoint` with invalid DOM point causes hitting `NS_ASSERTION()` 384 // and the number of times may be too many. (E.g., 1533913.html hits 385 // over 700 times!) We should just put warning instead. 386 if (NS_WARN_IF(!mRangeItem->mStartContainer)) { 387 mPoint.ref()->Clear(); 388 return; 389 } 390 // If the node was removed from the original document, clear the instance 391 // since the user should not keep handling the adopted or orphan node 392 // anymore. 393 if (NS_WARN_IF(mWasConnected && 394 !mRangeItem->mStartContainer->IsInComposedDoc()) || 395 NS_WARN_IF(mRangeItem->mStartContainer->OwnerDoc() != mDocument)) { 396 mPoint.ref()->Clear(); 397 return; 398 } 399 if (NS_WARN_IF(mRangeItem->mStartContainer->Length() < 400 mRangeItem->mStartOffset)) { 401 mPoint.ref()->SetToEndOf(mRangeItem->mStartContainer); 402 return; 403 } 404 mPoint.ref()->Set(mRangeItem->mStartContainer, mRangeItem->mStartOffset); 405 return; 406 } 407 mRangeUpdater.DropRangeItem(mRangeItem); 408 *mNode = mRangeItem->mStartContainer; 409 *mOffset = mRangeItem->mStartOffset; 410 if (!(*mNode)) { 411 return; 412 } 413 // If the node was removed from the original document, clear the instances 414 // since the user should not keep handling the adopted or orphan node 415 // anymore. 416 if (NS_WARN_IF(mWasConnected && !(*mNode)->IsInComposedDoc()) || 417 NS_WARN_IF((*mNode)->OwnerDoc() != mDocument)) { 418 *mNode = nullptr; 419 *mOffset = 0; 420 } 421 } 422 423 void FlushAndStopTracking() { Flush(StopTracking::Yes); } 424 425 void StopTracking() { mIsTracking = false; } 426 427 private: 428 RangeUpdater& mRangeUpdater; 429 // Allow tracking nsINode until nsNode is gone 430 nsCOMPtr<nsINode>* mNode; 431 uint32_t* mOffset; 432 Maybe<EditorDOMPoint*> mPoint; 433 OwningNonNull<RangeItem> mRangeItem; 434 RefPtr<dom::Document> mDocument; 435 bool mIsTracking = true; 436 bool mWasConnected; 437 }; 438 439 class MOZ_STACK_CLASS AutoTrackDOMRange final { 440 public: 441 AutoTrackDOMRange() = delete; 442 AutoTrackDOMRange(RangeUpdater& aRangeUpdater, EditorDOMPoint* aStartPoint, 443 EditorDOMPoint* aEndPoint) 444 : mRangeRefPtr(nullptr), mRangeOwningNonNull(nullptr) { 445 mStartPointTracker.emplace(aRangeUpdater, aStartPoint); 446 mEndPointTracker.emplace(aRangeUpdater, aEndPoint); 447 } 448 AutoTrackDOMRange(RangeUpdater& aRangeUpdater, EditorDOMRange* aRange) 449 : mRangeRefPtr(nullptr), mRangeOwningNonNull(nullptr) { 450 mStartPointTracker.emplace( 451 aRangeUpdater, const_cast<EditorDOMPoint*>(&aRange->StartRef())); 452 mEndPointTracker.emplace(aRangeUpdater, 453 const_cast<EditorDOMPoint*>(&aRange->EndRef())); 454 } 455 AutoTrackDOMRange(RangeUpdater& aRangeUpdater, const RefPtr<nsRange>* aRange) 456 : mStartPoint((*aRange)->StartRef()), 457 mEndPoint((*aRange)->EndRef()), 458 mRangeRefPtr(aRange), 459 mRangeOwningNonNull(nullptr) { 460 mStartPointTracker.emplace(aRangeUpdater, &mStartPoint); 461 mEndPointTracker.emplace(aRangeUpdater, &mEndPoint); 462 } 463 AutoTrackDOMRange(RangeUpdater& aRangeUpdater, 464 const OwningNonNull<nsRange>* aRange) 465 : mStartPoint((*aRange)->StartRef()), 466 mEndPoint((*aRange)->EndRef()), 467 mRangeRefPtr(nullptr), 468 mRangeOwningNonNull(aRange) { 469 mStartPointTracker.emplace(aRangeUpdater, &mStartPoint); 470 mEndPointTracker.emplace(aRangeUpdater, &mEndPoint); 471 } 472 ~AutoTrackDOMRange() { FlushAndStopTracking(); } 473 474 void FlushAndStopTracking() { 475 if (!mStartPointTracker && !mEndPointTracker) { 476 return; 477 } 478 mStartPointTracker.reset(); 479 mEndPointTracker.reset(); 480 if (!mRangeRefPtr && !mRangeOwningNonNull) { 481 // This must be created with EditorDOMRange or EditorDOMPoints. In the 482 // cases, destroying mStartPointTracker and mEndPointTracker has done 483 // everything which we need to do. 484 return; 485 } 486 // Otherwise, update the DOM ranges by ourselves. 487 if (mRangeRefPtr) { 488 if (!mStartPoint.IsSet() || !mEndPoint.IsSet()) { 489 (*mRangeRefPtr)->Reset(); 490 return; 491 } 492 (*mRangeRefPtr) 493 ->SetStartAndEnd(mStartPoint.ToRawRangeBoundary(), 494 mEndPoint.ToRawRangeBoundary()); 495 return; 496 } 497 if (mRangeOwningNonNull) { 498 if (!mStartPoint.IsSet() || !mEndPoint.IsSet()) { 499 (*mRangeOwningNonNull)->Reset(); 500 return; 501 } 502 (*mRangeOwningNonNull) 503 ->SetStartAndEnd(mStartPoint.ToRawRangeBoundary(), 504 mEndPoint.ToRawRangeBoundary()); 505 return; 506 } 507 } 508 509 void StopTracking() { 510 if (mStartPointTracker) { 511 mStartPointTracker->StopTracking(); 512 } 513 if (mEndPointTracker) { 514 mEndPointTracker->StopTracking(); 515 } 516 } 517 void StopTrackingStartBoundary() { 518 MOZ_ASSERT(!mRangeRefPtr, 519 "StopTrackingStartBoundary() is not available when tracking " 520 "RefPtr<nsRange>"); 521 MOZ_ASSERT(!mRangeOwningNonNull, 522 "StopTrackingStartBoundary() is not available when tracking " 523 "OwningNonNull<nsRange>"); 524 if (!mStartPointTracker) { 525 return; 526 } 527 mStartPointTracker->StopTracking(); 528 } 529 void StopTrackingEndBoundary() { 530 MOZ_ASSERT(!mRangeRefPtr, 531 "StopTrackingEndBoundary() is not available when tracking " 532 "RefPtr<nsRange>"); 533 MOZ_ASSERT(!mRangeOwningNonNull, 534 "StopTrackingEndBoundary() is not available when tracking " 535 "OwningNonNull<nsRange>"); 536 if (!mEndPointTracker) { 537 return; 538 } 539 mEndPointTracker->StopTracking(); 540 } 541 542 private: 543 Maybe<AutoTrackDOMPoint> mStartPointTracker; 544 Maybe<AutoTrackDOMPoint> mEndPointTracker; 545 EditorDOMPoint mStartPoint; 546 EditorDOMPoint mEndPoint; 547 const RefPtr<nsRange>* mRangeRefPtr; 548 const OwningNonNull<nsRange>* mRangeOwningNonNull; 549 }; 550 551 class MOZ_STACK_CLASS AutoTrackDOMMoveNodeResult final { 552 public: 553 AutoTrackDOMMoveNodeResult() = delete; 554 AutoTrackDOMMoveNodeResult(RangeUpdater& aRangeUpdater, 555 MoveNodeResult* aMoveNodeResult); 556 557 void FlushAndStopTracking() { 558 mTrackCaretPoint.FlushAndStopTracking(); 559 mTrackNextInsertionPoint.FlushAndStopTracking(); 560 mTrackMovedContentRange.FlushAndStopTracking(); 561 } 562 void StopTracking() { 563 mTrackCaretPoint.StopTracking(); 564 mTrackNextInsertionPoint.StopTracking(); 565 mTrackMovedContentRange.StopTracking(); 566 } 567 568 private: 569 AutoTrackDOMPoint mTrackCaretPoint; 570 AutoTrackDOMPoint mTrackNextInsertionPoint; 571 AutoTrackDOMRange mTrackMovedContentRange; 572 }; 573 574 class MOZ_STACK_CLASS AutoTrackDOMDeleteRangeResult final { 575 public: 576 AutoTrackDOMDeleteRangeResult() = delete; 577 AutoTrackDOMDeleteRangeResult(RangeUpdater& aRangeUpdater, 578 DeleteRangeResult* aDeleteRangeResult); 579 580 void FlushAndStopTracking() { 581 mTrackCaretPoint.FlushAndStopTracking(); 582 mTrackDeleteRange.FlushAndStopTracking(); 583 } 584 void StopTracking() { 585 mTrackCaretPoint.StopTracking(); 586 mTrackDeleteRange.StopTracking(); 587 } 588 589 private: 590 AutoTrackDOMPoint mTrackCaretPoint; 591 AutoTrackDOMRange mTrackDeleteRange; 592 }; 593 594 class MOZ_STACK_CLASS AutoTrackLineBreak final { 595 public: 596 AutoTrackLineBreak() = delete; 597 AutoTrackLineBreak(RangeUpdater& aRangeUpdater, EditorLineBreak* aLineBreak); 598 599 void FlushAndStopTracking(); 600 void StopTracking() { mTracker.StopTracking(); } 601 602 private: 603 EditorLineBreak* mLineBreak; 604 EditorDOMPoint mPoint; 605 AutoTrackDOMPoint mTracker; 606 }; 607 608 /** 609 * Another helper class for SelectionState. Stack based class for doing 610 * Will/DidReplaceContainer() 611 */ 612 613 class MOZ_STACK_CLASS AutoReplaceContainerSelNotify final { 614 public: 615 AutoReplaceContainerSelNotify() = delete; 616 // FYI: Marked as `MOZ_CAN_RUN_SCRIPT` for avoiding to use strong pointers 617 // for the members. 618 MOZ_CAN_RUN_SCRIPT 619 AutoReplaceContainerSelNotify(RangeUpdater& aRangeUpdater, 620 dom::Element& aOriginalElement, 621 dom::Element& aNewElement) 622 : mRangeUpdater(aRangeUpdater), 623 mOriginalElement(aOriginalElement), 624 mNewElement(aNewElement) { 625 mRangeUpdater.WillReplaceContainer(); 626 } 627 628 ~AutoReplaceContainerSelNotify() { 629 mRangeUpdater.DidReplaceContainer(mOriginalElement, mNewElement); 630 } 631 632 private: 633 RangeUpdater& mRangeUpdater; 634 dom::Element& mOriginalElement; 635 dom::Element& mNewElement; 636 }; 637 638 /** 639 * Another helper class for SelectionState. Stack based class for doing 640 * Will/DidRemoveContainer() 641 */ 642 643 class MOZ_STACK_CLASS AutoRemoveContainerSelNotify final { 644 public: 645 AutoRemoveContainerSelNotify() = delete; 646 AutoRemoveContainerSelNotify(RangeUpdater& aRangeUpdater, 647 const EditorRawDOMPoint& aAtRemovingElement) 648 : mRangeUpdater(aRangeUpdater), 649 mRemovingElement(*aAtRemovingElement.GetChild()->AsElement()), 650 mParentNode(*aAtRemovingElement.GetContainer()), 651 mOffsetInParent(aAtRemovingElement.Offset()), 652 mChildCountOfRemovingElement(mRemovingElement->GetChildCount()) { 653 MOZ_ASSERT(aAtRemovingElement.IsSet()); 654 mRangeUpdater.WillRemoveContainer(); 655 } 656 657 ~AutoRemoveContainerSelNotify() { 658 mRangeUpdater.DidRemoveContainer(mRemovingElement, mParentNode, 659 mOffsetInParent, 660 mChildCountOfRemovingElement); 661 } 662 663 private: 664 RangeUpdater& mRangeUpdater; 665 OwningNonNull<dom::Element> mRemovingElement; 666 OwningNonNull<nsINode> mParentNode; 667 uint32_t mOffsetInParent; 668 uint32_t mChildCountOfRemovingElement; 669 }; 670 671 /** 672 * Another helper class for SelectionState. Stack based class for doing 673 * Will/DidInsertContainer() 674 * XXX The lock state isn't useful if the edit action is triggered from 675 * a mutation event listener so that looks like that we can remove 676 * this class. 677 */ 678 679 class MOZ_STACK_CLASS AutoInsertContainerSelNotify final { 680 private: 681 RangeUpdater& mRangeUpdater; 682 683 public: 684 AutoInsertContainerSelNotify() = delete; 685 explicit AutoInsertContainerSelNotify(RangeUpdater& aRangeUpdater) 686 : mRangeUpdater(aRangeUpdater) { 687 mRangeUpdater.WillInsertContainer(); 688 } 689 690 ~AutoInsertContainerSelNotify() { mRangeUpdater.DidInsertContainer(); } 691 }; 692 693 /** 694 * Another helper class for SelectionState. Stack based class for doing 695 * DidMoveNode() 696 */ 697 698 class MOZ_STACK_CLASS AutoMoveNodeSelNotify final { 699 public: 700 using SimpleEditorDOMPoint = RangeUpdater::SimpleEditorDOMPoint; 701 702 AutoMoveNodeSelNotify() = delete; 703 explicit AutoMoveNodeSelNotify(RangeUpdater& aRangeUpdater, 704 const EditorRawDOMPoint& aExpectedDestination) 705 : mRangeUpdater(aRangeUpdater), 706 mExpectedDestination(aExpectedDestination.GetContainer(), nullptr, 707 aExpectedDestination.Offset()) {} 708 AutoMoveNodeSelNotify(RangeUpdater& aRangeUpdater, nsIContent& aContent, 709 const EditorRawDOMPoint& aExpectedDestination) 710 : mRangeUpdater(aRangeUpdater), 711 mExpectedDestination(aExpectedDestination.GetContainer(), nullptr, 712 aExpectedDestination.Offset()) { 713 if (aContent.GetParentNode()) { 714 mOldPoints.AppendElement(SimpleEditorDOMPoint( 715 &aContent, aContent.ComputeIndexInParentNode().valueOr(0))); 716 return; 717 } 718 mOldPoints.AppendElement(SimpleEditorDOMPoint(&aContent)); 719 } 720 721 void AppendContentWhichWillBeMoved(nsIContent& aContent) { 722 if (!mOldPoints.IsEmpty() && 723 mOldPoints.LastElement().GetNextSiblingOfChild() == &aContent) { 724 mOldPoints.AppendElement(SimpleEditorDOMPoint( 725 &aContent, mOldPoints.LastElement().Offset() + 1)); 726 return; 727 } 728 if (aContent.GetParentNode()) { 729 mOldPoints.AppendElement(SimpleEditorDOMPoint( 730 &aContent, aContent.ComputeIndexInParentNode().valueOr(0))); 731 return; 732 } 733 mOldPoints.AppendElement(SimpleEditorDOMPoint(&aContent)); 734 } 735 736 void DidMoveContent(nsIContent& aContent) { 737 if (!mNewPoints.IsEmpty() && 738 mNewPoints.LastElement().GetNextSiblingOfChild() == &aContent) { 739 mNewPoints.AppendElement(SimpleEditorDOMPoint( 740 &aContent, mNewPoints.LastElement().Offset() + 1)); 741 return; 742 } 743 // Compute offset when we need it. 744 mNewPoints.AppendElement(SimpleEditorDOMPoint(&aContent)); 745 } 746 747 ~AutoMoveNodeSelNotify() { 748 mRangeUpdater.DidMoveNodes(mOldPoints, mExpectedDestination, mNewPoints); 749 } 750 751 [[nodiscard]] size_t MovingContentCount() const { 752 return mOldPoints.Length(); 753 } 754 [[nodiscard]] nsIContent* GetContentAt(size_t index) const { 755 return mOldPoints[index].mChild; 756 } 757 758 private: 759 RangeUpdater& mRangeUpdater; 760 SimpleEditorDOMPoint mExpectedDestination; 761 AutoTArray<SimpleEditorDOMPoint, 12> mOldPoints; 762 AutoTArray<SimpleEditorDOMPoint, 12> mNewPoints; 763 }; 764 765 } // namespace mozilla 766 767 #endif // #ifndef mozilla_SelectionState_h