SelectionState.cpp (26196B)
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 #include "SelectionState.h" 7 8 #include "AutoClonedRangeArray.h" // for AutoClonedRangeArray 9 #include "EditorUtils.h" // for EditorUtils 10 #include "EditorLineBreak.h" // for EditorLineBreak 11 #include "HTMLEditHelpers.h" // for DeleteRangeResult 12 13 #include "ErrorList.h" 14 #include "mozilla/Assertions.h" // for MOZ_ASSERT, etc. 15 #include "mozilla/IntegerRange.h" // for IntegerRange 16 #include "mozilla/Likely.h" // For MOZ_LIKELY and MOZ_UNLIKELY 17 #include "mozilla/RangeUtils.h" // for RangeUtils 18 #include "mozilla/dom/RangeBinding.h" 19 #include "mozilla/dom/Selection.h" // for Selection 20 #include "nsAString.h" // for nsAString::Length 21 #include "nsCycleCollectionParticipant.h" 22 #include "nsDebug.h" // for NS_WARNING, etc. 23 #include "nsError.h" // for NS_OK, etc. 24 #include "nsIContent.h" // for nsIContent 25 #include "nsISupportsImpl.h" // for nsRange::Release 26 #include "nsRange.h" // for nsRange 27 28 namespace mozilla { 29 30 using namespace dom; 31 32 /***************************************************************************** 33 * mozilla::RangeItem 34 *****************************************************************************/ 35 36 nsINode* RangeItem::GetRoot() const { 37 if (MOZ_UNLIKELY(!IsPositioned())) { 38 return nullptr; 39 } 40 nsINode* rootNode = RangeUtils::ComputeRootNode(mStartContainer); 41 if (mStartContainer == mEndContainer) { 42 return rootNode; 43 } 44 return MOZ_LIKELY(rootNode == RangeUtils::ComputeRootNode(mEndContainer)) 45 ? rootNode 46 : nullptr; 47 } 48 49 /****************************************************************************** 50 * mozilla::SelectionState 51 * 52 * Class for recording selection info. Stores selection as collection of 53 * { {startnode, startoffset} , {endnode, endoffset} } tuples. Can't store 54 * ranges since dom gravity will possibly change the ranges. 55 ******************************************************************************/ 56 57 template nsresult RangeUpdater::SelAdjCreateNode(const EditorDOMPoint& aPoint); 58 template nsresult RangeUpdater::SelAdjCreateNode( 59 const EditorRawDOMPoint& aPoint); 60 template nsresult RangeUpdater::SelAdjInsertNode(const EditorDOMPoint& aPoint); 61 template nsresult RangeUpdater::SelAdjInsertNode( 62 const EditorRawDOMPoint& aPoint); 63 64 SelectionState::SelectionState(const AutoClonedSelectionRangeArray& aRanges) 65 : mDirection(aRanges.GetDirection()) { 66 mArray.SetCapacity(aRanges.Ranges().Length()); 67 for (const OwningNonNull<nsRange>& range : aRanges.Ranges()) { 68 RefPtr<RangeItem> rangeItem = new RangeItem(); 69 rangeItem->StoreRange(range); 70 mArray.AppendElement(std::move(rangeItem)); 71 } 72 } 73 74 void SelectionState::SaveSelection(Selection& aSelection) { 75 // if we need more items in the array, new them 76 if (mArray.Length() < aSelection.RangeCount()) { 77 for (uint32_t i = mArray.Length(); i < aSelection.RangeCount(); i++) { 78 mArray.AppendElement(); 79 mArray[i] = new RangeItem(); 80 } 81 } else if (mArray.Length() > aSelection.RangeCount()) { 82 // else if we have too many, delete them 83 mArray.TruncateLength(aSelection.RangeCount()); 84 } 85 86 // now store the selection ranges 87 const uint32_t rangeCount = aSelection.RangeCount(); 88 for (const uint32_t i : IntegerRange(rangeCount)) { 89 MOZ_ASSERT(aSelection.RangeCount() == rangeCount); 90 const nsRange* range = aSelection.GetRangeAt(i); 91 MOZ_ASSERT(range); 92 if (MOZ_UNLIKELY(NS_WARN_IF(!range))) { 93 continue; 94 } 95 mArray[i]->StoreRange(*range); 96 } 97 98 mDirection = aSelection.GetDirection(); 99 } 100 101 nsresult SelectionState::RestoreSelection(Selection& aSelection) { 102 // clear out selection 103 IgnoredErrorResult ignoredError; 104 aSelection.RemoveAllRanges(ignoredError); 105 NS_WARNING_ASSERTION(!ignoredError.Failed(), 106 "Selection::RemoveAllRanges() failed, but ignored"); 107 108 aSelection.SetDirection(mDirection); 109 110 ErrorResult error; 111 const CopyableAutoTArray<RefPtr<RangeItem>, 10> rangeItems(mArray); 112 for (const RefPtr<RangeItem>& rangeItem : rangeItems) { 113 RefPtr<nsRange> range = rangeItem->GetRange(); 114 if (!range) { 115 NS_WARNING("RangeItem::GetRange() failed"); 116 return NS_ERROR_FAILURE; 117 } 118 aSelection.AddRangeAndSelectFramesAndNotifyListeners(*range, error); 119 if (error.Failed()) { 120 NS_WARNING( 121 "Selection::AddRangeAndSelectFramesAndNotifyListeners() failed"); 122 return error.StealNSResult(); 123 } 124 } 125 return NS_OK; 126 } 127 128 void SelectionState::ApplyTo(AutoClonedSelectionRangeArray& aRanges) { 129 aRanges.RemoveAllRanges(); 130 aRanges.SetDirection(mDirection); 131 for (const RefPtr<RangeItem>& rangeItem : mArray) { 132 RefPtr<nsRange> range = rangeItem->GetRange(); 133 if (MOZ_UNLIKELY(!range)) { 134 continue; 135 } 136 aRanges.Ranges().AppendElement(std::move(range)); 137 } 138 } 139 140 bool SelectionState::Equals(const SelectionState& aOther) const { 141 if (mArray.Length() != aOther.mArray.Length()) { 142 return false; 143 } 144 if (mArray.IsEmpty()) { 145 return false; // XXX Why? 146 } 147 if (mDirection != aOther.mDirection) { 148 return false; 149 } 150 151 for (uint32_t i : IntegerRange(mArray.Length())) { 152 if (NS_WARN_IF(!mArray[i]) || NS_WARN_IF(!aOther.mArray[i]) || 153 !mArray[i]->Equals(*aOther.mArray[i])) { 154 return false; 155 } 156 } 157 // if we got here, they are equal 158 return true; 159 } 160 161 /****************************************************************************** 162 * mozilla::RangeUpdater 163 * 164 * Class for updating nsRanges in response to editor actions. 165 ******************************************************************************/ 166 167 RangeUpdater::RangeUpdater() : mLocked(false) {} 168 169 void RangeUpdater::RegisterRangeItem(RangeItem& aRangeItem) { 170 if (mArray.Contains(&aRangeItem)) { 171 NS_ERROR("tried to register an already registered range"); 172 return; // don't register it again. It would get doubly adjusted. 173 } 174 mArray.AppendElement(&aRangeItem); 175 } 176 177 void RangeUpdater::DropRangeItem(RangeItem& aRangeItem) { 178 NS_WARNING_ASSERTION( 179 mArray.Contains(&aRangeItem), 180 "aRangeItem is not in the range, but tried to removed from it"); 181 mArray.RemoveElement(&aRangeItem); 182 } 183 184 void RangeUpdater::RegisterSelectionState(SelectionState& aSelectionState) { 185 for (RefPtr<RangeItem>& rangeItem : aSelectionState.mArray) { 186 if (NS_WARN_IF(!rangeItem)) { 187 continue; 188 } 189 RegisterRangeItem(*rangeItem); 190 } 191 } 192 193 void RangeUpdater::DropSelectionState(SelectionState& aSelectionState) { 194 for (RefPtr<RangeItem>& rangeItem : aSelectionState.mArray) { 195 if (NS_WARN_IF(!rangeItem)) { 196 continue; 197 } 198 DropRangeItem(*rangeItem); 199 } 200 } 201 202 // gravity methods: 203 204 template <typename PT, typename CT> 205 nsresult RangeUpdater::SelAdjCreateNode( 206 const EditorDOMPointBase<PT, CT>& aPoint) { 207 if (mLocked) { 208 // lock set by Will/DidReplaceParent, etc... 209 return NS_OK; 210 } 211 if (mArray.IsEmpty()) { 212 return NS_OK; 213 } 214 215 if (NS_WARN_IF(!aPoint.IsSetAndValid())) { 216 return NS_ERROR_INVALID_ARG; 217 } 218 219 for (RefPtr<RangeItem>& rangeItem : mArray) { 220 if (NS_WARN_IF(!rangeItem)) { 221 return NS_ERROR_FAILURE; 222 } 223 if (rangeItem->mStartContainer == aPoint.GetContainer() && 224 rangeItem->mStartOffset > aPoint.Offset()) { 225 rangeItem->mStartOffset++; 226 } 227 if (rangeItem->mEndContainer == aPoint.GetContainer() && 228 rangeItem->mEndOffset > aPoint.Offset()) { 229 rangeItem->mEndOffset++; 230 } 231 } 232 return NS_OK; 233 } 234 235 template <typename PT, typename CT> 236 nsresult RangeUpdater::SelAdjInsertNode( 237 const EditorDOMPointBase<PT, CT>& aPoint) { 238 nsresult rv = SelAdjCreateNode(aPoint); 239 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 240 "RangeUpdater::SelAdjCreateNode() failed"); 241 return rv; 242 } 243 244 void RangeUpdater::SelAdjDeleteNode(nsINode& aNodeToDelete) { 245 if (mLocked) { 246 // lock set by Will/DidReplaceParent, etc... 247 return; 248 } 249 250 if (mArray.IsEmpty()) { 251 return; 252 } 253 254 EditorRawDOMPoint atNodeToDelete(&aNodeToDelete); 255 NS_ASSERTION(atNodeToDelete.IsSetAndValid(), 256 "aNodeToDelete must be an orphan node or this is called " 257 "during mutation"); 258 // check for range endpoints that are after aNodeToDelete and in the same 259 // parent 260 for (RefPtr<RangeItem>& rangeItem : mArray) { 261 MOZ_ASSERT(rangeItem); 262 263 if (rangeItem->mStartContainer == atNodeToDelete.GetContainer() && 264 rangeItem->mStartOffset > atNodeToDelete.Offset()) { 265 rangeItem->mStartOffset--; 266 } 267 if (rangeItem->mEndContainer == atNodeToDelete.GetContainer() && 268 rangeItem->mEndOffset > atNodeToDelete.Offset()) { 269 rangeItem->mEndOffset--; 270 } 271 272 // check for range endpoints that are in aNodeToDelete 273 if (rangeItem->mStartContainer == &aNodeToDelete) { 274 rangeItem->mStartContainer = atNodeToDelete.GetContainer(); 275 rangeItem->mStartOffset = atNodeToDelete.Offset(); 276 } 277 if (rangeItem->mEndContainer == &aNodeToDelete) { 278 rangeItem->mEndContainer = atNodeToDelete.GetContainer(); 279 rangeItem->mEndOffset = atNodeToDelete.Offset(); 280 } 281 282 // check for range endpoints that are in descendants of aNodeToDelete 283 bool updateEndBoundaryToo = false; 284 if (EditorUtils::IsDescendantOf(*rangeItem->mStartContainer, 285 aNodeToDelete)) { 286 updateEndBoundaryToo = 287 rangeItem->mStartContainer == rangeItem->mEndContainer; 288 rangeItem->mStartContainer = atNodeToDelete.GetContainer(); 289 rangeItem->mStartOffset = atNodeToDelete.Offset(); 290 } 291 292 // avoid having to call IsDescendantOf() for common case of range startnode 293 // == range endnode. 294 if (updateEndBoundaryToo || 295 EditorUtils::IsDescendantOf(*rangeItem->mEndContainer, aNodeToDelete)) { 296 rangeItem->mEndContainer = atNodeToDelete.GetContainer(); 297 rangeItem->mEndOffset = atNodeToDelete.Offset(); 298 } 299 } 300 } 301 302 nsresult RangeUpdater::SelAdjSplitNode(nsIContent& aOriginalContent, 303 uint32_t aSplitOffset, 304 nsIContent& aNewContent) { 305 if (mLocked) { 306 // lock set by Will/DidReplaceParent, etc... 307 return NS_OK; 308 } 309 310 if (mArray.IsEmpty()) { 311 return NS_OK; 312 } 313 314 EditorRawDOMPoint atNewNode(&aNewContent); 315 if (NS_WARN_IF(!atNewNode.IsSetAndValid())) { 316 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE; 317 } 318 319 auto AdjustDOMPoint = [&](nsCOMPtr<nsINode>& aContainer, 320 uint32_t& aOffset) -> void { 321 if (aContainer == atNewNode.GetContainer()) { 322 // When we create a right node, we insert it after the left node. 323 // In this case, 324 // - `{}<left/>` should become `{}<left/><right/>` (0 -> 0) 325 // - `<left/>{}` should become `<left/><right/>{}` (1 -> 2) 326 // - `{<left/>}` should become `{<left/><right/>}` (0 -> 0, 1 -> 2} 327 // Therefore, we need to increate the offset only when the offset equals 328 // or is larger than the offset at the right node. 329 if (aOffset >= atNewNode.Offset()) { 330 aOffset++; 331 } 332 } 333 // If point is in the range which are moved from aOriginalContent to 334 // aNewContent, we need to change its container to aNewContent and may need 335 // to adjust the offset. If point is in the range which are not moved from 336 // aOriginalContent, we may need to adjust the offset. 337 if (aContainer != &aOriginalContent) { 338 return; 339 } 340 if (aOffset >= aSplitOffset) { 341 aContainer = &aNewContent; 342 aOffset -= aSplitOffset; 343 } 344 }; 345 346 for (RefPtr<RangeItem>& rangeItem : mArray) { 347 if (NS_WARN_IF(!rangeItem)) { 348 return NS_ERROR_FAILURE; 349 } 350 AdjustDOMPoint(rangeItem->mStartContainer, rangeItem->mStartOffset); 351 AdjustDOMPoint(rangeItem->mEndContainer, rangeItem->mEndOffset); 352 } 353 return NS_OK; 354 } 355 356 nsresult RangeUpdater::SelAdjJoinNodes( 357 const EditorRawDOMPoint& aStartOfRightContent, 358 const nsIContent& aRemovedContent, 359 const EditorDOMPoint& aOldPointAtRightContent) { 360 MOZ_ASSERT(aStartOfRightContent.IsSetAndValid()); 361 MOZ_ASSERT(aOldPointAtRightContent.IsSet()); // Invalid point in most cases 362 MOZ_ASSERT(aOldPointAtRightContent.HasOffset()); 363 364 if (mLocked) { 365 // lock set by Will/DidReplaceParent, etc... 366 return NS_OK; 367 } 368 369 if (mArray.IsEmpty()) { 370 return NS_OK; 371 } 372 373 auto AdjustDOMPoint = [&](nsCOMPtr<nsINode>& aContainer, 374 uint32_t& aOffset) -> void { 375 // FYI: Typically, containers of aOldPointAtRightContent and 376 // aStartOfRightContent are same. They are different when one of the 377 // node was moved to somewhere and they are joined by undoing splitting 378 // a node. 379 if (aContainer == &aRemovedContent) { 380 // If the point is in the removed content, move the point to the new 381 // point in the joined node. If left node content is moved into 382 // right node, the offset should be same. Otherwise, we need to advance 383 // the offset to length of the removed content. 384 aContainer = aStartOfRightContent.GetContainer(); 385 aOffset += aStartOfRightContent.Offset(); 386 } 387 // TODO: If aOldPointAtRightContent.GetContainer() was in aRemovedContent, 388 // we fail to adjust container and offset here because we need to 389 // make point to where aRemoveContent was. However, collecting all 390 // ancestors of the right content may be expensive. What's the best 391 // approach to fix this? 392 else if (aContainer == aOldPointAtRightContent.GetContainer()) { 393 // If the point is in common parent of joined content nodes and it 394 // pointed after the right content node, decrease the offset. 395 if (aOffset > aOldPointAtRightContent.Offset()) { 396 aOffset--; 397 } 398 // If it pointed the right content node, adjust it to point ex-first 399 // content of the right node. 400 else if (aOffset == aOldPointAtRightContent.Offset()) { 401 aContainer = aStartOfRightContent.GetContainer(); 402 aOffset = aStartOfRightContent.Offset(); 403 } 404 } 405 }; 406 407 for (RefPtr<RangeItem>& rangeItem : mArray) { 408 if (NS_WARN_IF(!rangeItem)) { 409 return NS_ERROR_FAILURE; 410 } 411 AdjustDOMPoint(rangeItem->mStartContainer, rangeItem->mStartOffset); 412 AdjustDOMPoint(rangeItem->mEndContainer, rangeItem->mEndOffset); 413 } 414 415 return NS_OK; 416 } 417 418 void RangeUpdater::SelAdjReplaceText(const Text& aTextNode, uint32_t aOffset, 419 uint32_t aReplacedLength, 420 uint32_t aInsertedLength) { 421 if (mLocked) { 422 // lock set by Will/DidReplaceParent, etc... 423 return; 424 } 425 426 // First, adjust selection for insertion because when offset is in the 427 // replaced range, it's adjusted to aOffset and never modified by the 428 // insertion if we adjust selection for deletion first. 429 SelAdjInsertText(aTextNode, aOffset, aInsertedLength); 430 431 // Then, adjust selection for deletion. 432 SelAdjDeleteText(aTextNode, aOffset, aReplacedLength); 433 } 434 435 void RangeUpdater::SelAdjInsertText(const Text& aTextNode, uint32_t aOffset, 436 uint32_t aInsertedLength) { 437 if (mLocked) { 438 // lock set by Will/DidReplaceParent, etc... 439 return; 440 } 441 442 for (RefPtr<RangeItem>& rangeItem : mArray) { 443 MOZ_ASSERT(rangeItem); 444 445 if (rangeItem->mStartContainer == &aTextNode && 446 rangeItem->mStartOffset > aOffset) { 447 rangeItem->mStartOffset += aInsertedLength; 448 } 449 if (rangeItem->mEndContainer == &aTextNode && 450 rangeItem->mEndOffset > aOffset) { 451 rangeItem->mEndOffset += aInsertedLength; 452 } 453 } 454 } 455 456 void RangeUpdater::SelAdjDeleteText(const Text& aTextNode, uint32_t aOffset, 457 uint32_t aDeletedLength) { 458 if (mLocked) { 459 // lock set by Will/DidReplaceParent, etc... 460 return; 461 } 462 463 for (RefPtr<RangeItem>& rangeItem : mArray) { 464 MOZ_ASSERT(rangeItem); 465 466 if (rangeItem->mStartContainer == &aTextNode && 467 rangeItem->mStartOffset > aOffset) { 468 if (rangeItem->mStartOffset >= aDeletedLength) { 469 rangeItem->mStartOffset -= aDeletedLength; 470 } else { 471 rangeItem->mStartOffset = 0; 472 } 473 } 474 if (rangeItem->mEndContainer == &aTextNode && 475 rangeItem->mEndOffset > aOffset) { 476 if (rangeItem->mEndOffset >= aDeletedLength) { 477 rangeItem->mEndOffset -= aDeletedLength; 478 } else { 479 rangeItem->mEndOffset = 0; 480 } 481 } 482 } 483 } 484 485 void RangeUpdater::DidReplaceContainer(const Element& aRemovedElement, 486 Element& aInsertedElement) { 487 if (NS_WARN_IF(!mLocked)) { 488 return; 489 } 490 mLocked = false; 491 492 for (RefPtr<RangeItem>& rangeItem : mArray) { 493 if (NS_WARN_IF(!rangeItem)) { 494 return; 495 } 496 497 if (rangeItem->mStartContainer == &aRemovedElement) { 498 rangeItem->mStartContainer = &aInsertedElement; 499 } 500 if (rangeItem->mEndContainer == &aRemovedElement) { 501 rangeItem->mEndContainer = &aInsertedElement; 502 } 503 } 504 } 505 506 void RangeUpdater::DidRemoveContainer(const Element& aRemovedElement, 507 nsINode& aRemovedElementContainerNode, 508 uint32_t aOldOffsetOfRemovedElement, 509 uint32_t aOldChildCountOfRemovedElement) { 510 if (NS_WARN_IF(!mLocked)) { 511 return; 512 } 513 mLocked = false; 514 515 for (RefPtr<RangeItem>& rangeItem : mArray) { 516 if (NS_WARN_IF(!rangeItem)) { 517 return; 518 } 519 520 if (rangeItem->mStartContainer == &aRemovedElement) { 521 rangeItem->mStartContainer = &aRemovedElementContainerNode; 522 rangeItem->mStartOffset += aOldOffsetOfRemovedElement; 523 } else if (rangeItem->mStartContainer == &aRemovedElementContainerNode && 524 rangeItem->mStartOffset > aOldOffsetOfRemovedElement) { 525 rangeItem->mStartOffset += aOldChildCountOfRemovedElement - 1; 526 } 527 528 if (rangeItem->mEndContainer == &aRemovedElement) { 529 rangeItem->mEndContainer = &aRemovedElementContainerNode; 530 rangeItem->mEndOffset += aOldOffsetOfRemovedElement; 531 } else if (rangeItem->mEndContainer == &aRemovedElementContainerNode && 532 rangeItem->mEndOffset > aOldOffsetOfRemovedElement) { 533 rangeItem->mEndOffset += aOldChildCountOfRemovedElement - 1; 534 } 535 } 536 } 537 538 void RangeUpdater::DidMoveNodes( 539 const nsTArray<SimpleEditorDOMPoint>& aOldPoints, 540 const SimpleEditorDOMPoint& aExpectedDestination, 541 const nsTArray<SimpleEditorDOMPoint>& aNewPoints) { 542 if (mLocked) { 543 // Do nothing if moving nodes is occurred while changing the container. 544 return; 545 } 546 547 AutoTArray<SimpleEditorRawDOMPoint, 12> oldPoints; 548 oldPoints.SetCapacity(aOldPoints.Length()); 549 for (const size_t i : IntegerRange(aOldPoints.Length())) { 550 const SimpleEditorDOMPoint& oldPoint = aOldPoints[i]; 551 if (MOZ_UNLIKELY(oldPoints.IsEmpty())) { 552 oldPoints.AppendElement(SimpleEditorRawDOMPoint( 553 oldPoint.mContainer, oldPoint.mChild, oldPoint.Offset())); 554 continue; 555 } 556 // Adjust offset as if preceding children removed before because if each 557 // node is moved separately, the old offset does not contain the old 558 // previous siblings which were moved togather. Therefore, if moved in the 559 // same container and before the offset, we don't need to adjust the offset. 560 // If we moved to previous point in the same container, the old point does 561 // not need to be adjusted. 562 if (MOZ_UNLIKELY(aExpectedDestination.mContainer == oldPoint.mContainer) && 563 aExpectedDestination.Offset() < oldPoint.Offset()) { 564 oldPoints.AppendElement(SimpleEditorRawDOMPoint( 565 oldPoint.mContainer, oldPoint.mChild, oldPoint.Offset())); 566 continue; 567 } 568 // If we moved the next sibling of the previously moved content, the 569 // sibling start position is same as the previous one. 570 if (oldPoints.LastElement().mChild->GetNextSibling() == oldPoint.mChild) { 571 oldPoints.AppendElement( 572 SimpleEditorRawDOMPoint(oldPoint.mContainer, oldPoint.mChild, 573 oldPoints.LastElement().Offset())); 574 continue; 575 } 576 // If the moved content was not the next sibling of the previous moved 577 // content, we need to count the number of previous siblings which we 578 // moved and decrease the count from the offset. That's the offset if we 579 // moved each node separately. 580 uint32_t offset = oldPoint.Offset(); 581 for (const SimpleEditorRawDOMPoint& precedingPoint : oldPoints) { 582 if (precedingPoint.mContainer == oldPoint.mContainer && 583 precedingPoint.Offset() < oldPoint.Offset()) { 584 offset--; 585 } 586 } 587 oldPoints.AppendElement( 588 SimpleEditorRawDOMPoint{oldPoint.mContainer, oldPoint.mChild, offset}); 589 } 590 591 size_t newPointIndex = 0; 592 for (const SimpleEditorRawDOMPoint& oldPoint : oldPoints) { 593 const SimpleEditorDOMPoint* newPoint = 594 newPointIndex < aNewPoints.Length() && 595 oldPoint.mChild == aNewPoints[newPointIndex].mChild 596 ? &aNewPoints[newPointIndex++] 597 : nullptr; 598 auto AdjustDOMPoint = [&](nsCOMPtr<nsINode>& aNode, uint32_t& aOffset) { 599 // If the node was removed, we should adjust the point around aOldPoint. 600 if (!newPoint || !newPoint->mContainer) { 601 // If the point was in the removed container, the point should be moved 602 // where the node was. 603 if (aNode->IsInclusiveDescendantOf(oldPoint.mChild)) { 604 aNode = oldPoint.mContainer; 605 aOffset = std::min(oldPoint.Offset(), aNode->Length()); 606 return; 607 } 608 // If the point was where the node was or latter, fix the position. 609 if (aNode == oldPoint.mContainer) { 610 if (aOffset > oldPoint.Offset()) { 611 aOffset--; 612 } 613 if (aOffset > aNode->Length()) { 614 aOffset = aNode->Length(); 615 } 616 return; 617 } 618 return; 619 } 620 // The node was moved to another point, at least. 621 if (aNode == oldPoint.mContainer) { 622 // If previously pointed the moved content, it should keep pointing it. 623 if (aOffset == oldPoint.Offset()) { 624 aNode = newPoint->mContainer; 625 aOffset = newPoint->Offset(); 626 } else if (aOffset > oldPoint.Offset()) { 627 aOffset--; 628 } 629 return; 630 } 631 if (aNode == newPoint->mContainer) { 632 if (aOffset > newPoint->Offset()) { 633 aOffset++; 634 } 635 } 636 }; 637 for (RefPtr<RangeItem>& rangeItem : mArray) { 638 if (NS_WARN_IF(!rangeItem)) { 639 return; 640 } 641 642 AdjustDOMPoint(rangeItem->mStartContainer, rangeItem->mStartOffset); 643 AdjustDOMPoint(rangeItem->mEndContainer, rangeItem->mEndOffset); 644 } 645 } 646 } 647 648 /****************************************************************************** 649 * mozilla::RangeItem 650 * 651 * Helper struct for SelectionState. This stores range endpoints. 652 ******************************************************************************/ 653 654 NS_IMPL_CYCLE_COLLECTION(RangeItem, mStartContainer, mEndContainer) 655 656 void RangeItem::StoreRange(const nsRange& aRange) { 657 mStartContainer = aRange.GetStartContainer(); 658 mStartOffset = aRange.StartOffset(); 659 mEndContainer = aRange.GetEndContainer(); 660 mEndOffset = aRange.EndOffset(); 661 } 662 663 already_AddRefed<nsRange> RangeItem::GetRange() const { 664 RefPtr<nsRange> range = nsRange::Create( 665 mStartContainer, mStartOffset, mEndContainer, mEndOffset, IgnoreErrors()); 666 NS_WARNING_ASSERTION(range, "nsRange::Create() failed"); 667 return range.forget(); 668 } 669 670 /****************************************************************************** 671 * mozilla::AutoTrackDOMPoint 672 ******************************************************************************/ 673 674 AutoTrackDOMPoint::AutoTrackDOMPoint(RangeUpdater& aRangeUpdater, 675 CaretPoint* aCaretPoint) 676 : AutoTrackDOMPoint(aRangeUpdater, &aCaretPoint->mCaretPoint) {} 677 678 /****************************************************************************** 679 * mozilla::AutoTrackDOMMoveNodeResult 680 ******************************************************************************/ 681 682 AutoTrackDOMMoveNodeResult::AutoTrackDOMMoveNodeResult( 683 RangeUpdater& aRangeUpdater, MoveNodeResult* aMoveNodeResult) 684 : mTrackCaretPoint(aRangeUpdater, 685 static_cast<CaretPoint*>(aMoveNodeResult)), 686 mTrackNextInsertionPoint(aRangeUpdater, 687 &aMoveNodeResult->mNextInsertionPoint), 688 mTrackMovedContentRange(aRangeUpdater, 689 &aMoveNodeResult->mMovedContentRange) {} 690 691 /****************************************************************************** 692 * mozilla::AutoTrackDeleteRangeResult 693 ******************************************************************************/ 694 695 AutoTrackDOMDeleteRangeResult::AutoTrackDOMDeleteRangeResult( 696 RangeUpdater& aRangeUpdater, DeleteRangeResult* aDeleteRangeResult) 697 : mTrackCaretPoint(aRangeUpdater, 698 static_cast<CaretPoint*>(aDeleteRangeResult)), 699 mTrackDeleteRange(aRangeUpdater, &aDeleteRangeResult->mDeleteRange) {} 700 701 /****************************************************************************** 702 * mozilla::AutoTrackLineBreak 703 ******************************************************************************/ 704 705 AutoTrackLineBreak::AutoTrackLineBreak(RangeUpdater& aRangeUpdater, 706 EditorLineBreak* aLineBreak) 707 : mLineBreak(aLineBreak->IsPreformattedLineBreak() ? aLineBreak : nullptr), 708 mPoint(mLineBreak ? mLineBreak->To<EditorDOMPoint>() : EditorDOMPoint()), 709 mTracker(aRangeUpdater, &mPoint) { 710 MOZ_ASSERT(aLineBreak->IsPreformattedLineBreak()); 711 } 712 713 void AutoTrackLineBreak::FlushAndStopTracking() { 714 if (!mLineBreak) { 715 return; 716 } 717 mTracker.FlushAndStopTracking(); 718 if (mPoint.GetContainer() == mLineBreak->mContent) { 719 mLineBreak->mOffsetInText = Some(mPoint.Offset()); 720 } 721 mLineBreak = nullptr; 722 } 723 724 } // namespace mozilla