tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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