tor-browser

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

MoveSiblingsTransaction.cpp (16672B)


      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 "MoveNodeTransaction.h"
      7 
      8 #include "EditorBase.h"           // for EditorBase
      9 #include "EditorDOMAPIWrapper.h"  // for AutoNodeAPIWrapper
     10 #include "EditorDOMPoint.h"       // for EditorDOMPoint
     11 #include "HTMLEditor.h"           // for HTMLEditor
     12 #include "HTMLEditUtils.h"        // for HTMLEditUtils
     13 
     14 #include "mozilla/Likely.h"
     15 #include "mozilla/Logging.h"
     16 #include "mozilla/ToString.h"
     17 
     18 #include "nsDebug.h"     // for NS_WARNING, etc.
     19 #include "nsError.h"     // for NS_ERROR_NULL_POINTER, etc.
     20 #include "nsIContent.h"  // for nsIContent
     21 #include "nsString.h"    // for nsString
     22 
     23 namespace mozilla {
     24 
     25 using namespace dom;
     26 
     27 template already_AddRefed<MoveSiblingsTransaction>
     28 MoveSiblingsTransaction::MaybeCreate(HTMLEditor& aHTMLEditor,
     29                                     nsIContent& aFirstContentToMove,
     30                                     nsIContent& aLastContentToMove,
     31                                     const EditorDOMPoint& aPointToInsert);
     32 template already_AddRefed<MoveSiblingsTransaction>
     33 MoveSiblingsTransaction::MaybeCreate(HTMLEditor& aHTMLEditor,
     34                                     nsIContent& aFirstContentToMove,
     35                                     nsIContent& aLastContentToMove,
     36                                     const EditorRawDOMPoint& aPointToInsert);
     37 
     38 // static
     39 template <typename PT, typename CT>
     40 already_AddRefed<MoveSiblingsTransaction> MoveSiblingsTransaction::MaybeCreate(
     41    HTMLEditor& aHTMLEditor, nsIContent& aFirstContentToMove,
     42    nsIContent& aLastContentToMove,
     43    const EditorDOMPointBase<PT, CT>& aPointToInsert) {
     44  if (NS_WARN_IF(!aFirstContentToMove.GetParentNode()) ||
     45      NS_WARN_IF(&aFirstContentToMove == &aLastContentToMove) ||
     46      NS_WARN_IF(aFirstContentToMove.GetParentNode() !=
     47                 aLastContentToMove.GetParentNode()) ||
     48      NS_WARN_IF(!aPointToInsert.IsSet())) {
     49    return nullptr;
     50  }
     51 
     52  // The destination should be editable, but it may be in an orphan node
     53  // or sub-tree to reduce number of DOM mutation events.  In such case,
     54  // we're okay to move a node into the non-editable content because we
     55  // can assume that the caller will insert it into an editable element.
     56  if (NS_WARN_IF(aPointToInsert.IsInComposedDoc() &&
     57                 !HTMLEditUtils::IsSimplyEditableNode(
     58                     *aPointToInsert.GetContainer()))) {
     59    return nullptr;
     60  }
     61  const uint32_t numberOfSiblings = [&]() -> uint32_t {
     62    uint32_t num = 1;
     63    for (nsIContent* content = aFirstContentToMove.GetNextSibling(); content;
     64         content = content->GetNextSibling()) {
     65      // TODO: We should not allow to move a node to improper container element.
     66      //       However, this is currently used to move invalid parent while
     67      //       processing the nodes.  Therefore, treating the case as error
     68      //       breaks a lot.
     69      if (NS_WARN_IF(content->IsInComposedDoc() &&
     70                     !HTMLEditUtils::IsRemovableNode(*content))) {
     71        return 0;
     72      }
     73      num++;
     74      if (content == &aLastContentToMove) {
     75        return num;
     76      }
     77    }
     78    return 0;
     79  }();
     80  if (NS_WARN_IF(!numberOfSiblings)) {
     81    return nullptr;
     82  }
     83  RefPtr<MoveSiblingsTransaction> transaction = new MoveSiblingsTransaction(
     84      aHTMLEditor, aFirstContentToMove, aLastContentToMove, numberOfSiblings,
     85      aPointToInsert);
     86  return transaction.forget();
     87 }
     88 
     89 template <typename PT, typename CT>
     90 MoveSiblingsTransaction::MoveSiblingsTransaction(
     91    HTMLEditor& aHTMLEditor, nsIContent& aFirstContentToMove,
     92    nsIContent& aLastContentToMove, uint32_t aNumberOfSiblings,
     93    const EditorDOMPointBase<PT, CT>& aPointToInsert)
     94    : MoveNodeTransactionBase(aHTMLEditor, aLastContentToMove,
     95                              aPointToInsert.template To<EditorRawDOMPoint>()) {
     96  mSiblingsToMove.SetCapacity(aNumberOfSiblings);
     97  for (nsIContent* content = &aFirstContentToMove; content;
     98       content = content->GetNextSibling()) {
     99    mSiblingsToMove.AppendElement(*content);
    100    if (content == &aLastContentToMove) {
    101      break;
    102    }
    103  }
    104  MOZ_ASSERT(mSiblingsToMove.Length() == aNumberOfSiblings);
    105 }
    106 
    107 std::ostream& operator<<(std::ostream& aStream,
    108                         const MoveSiblingsTransaction& aTransaction) {
    109  auto DumpNodeDetails = [&](const nsINode* aNode) {
    110    if (aNode) {
    111      if (aNode->IsText()) {
    112        nsAutoString data;
    113        aNode->AsText()->GetData(data);
    114        aStream << " (#text \"" << NS_ConvertUTF16toUTF8(data).get() << "\")";
    115      } else {
    116        aStream << " (" << *aNode << ")";
    117      }
    118    }
    119  };
    120  aStream << "{ mSiblingsToMove[0]=" << aTransaction.mSiblingsToMove[0].get();
    121  DumpNodeDetails(aTransaction.mSiblingsToMove[0]);
    122  aStream << ", mSiblingsToMove[" << aTransaction.mSiblingsToMove.Length() - 1
    123          << aTransaction.mSiblingsToMove.LastElement().get();
    124  DumpNodeDetails(aTransaction.mSiblingsToMove.LastElement());
    125  aStream << ", mContainer=" << aTransaction.mContainer.get();
    126  DumpNodeDetails(aTransaction.mContainer);
    127  aStream << ", mReference=" << aTransaction.mReference.get();
    128  DumpNodeDetails(aTransaction.mReference);
    129  aStream << ", mOldContainer=" << aTransaction.mOldContainer.get();
    130  DumpNodeDetails(aTransaction.mOldContainer);
    131  aStream << ", mOldNextSibling=" << aTransaction.mOldNextSibling.get();
    132  DumpNodeDetails(aTransaction.mOldNextSibling);
    133  aStream << ", mHTMLEditor=" << aTransaction.mHTMLEditor.get() << " }";
    134  return aStream;
    135 }
    136 
    137 NS_IMPL_CYCLE_COLLECTION_INHERITED(MoveSiblingsTransaction, EditTransactionBase,
    138                                   mSiblingsToMove)
    139 
    140 NS_IMPL_ADDREF_INHERITED(MoveSiblingsTransaction, EditTransactionBase)
    141 NS_IMPL_RELEASE_INHERITED(MoveSiblingsTransaction, EditTransactionBase)
    142 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MoveSiblingsTransaction)
    143 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
    144 
    145 NS_IMETHODIMP MoveSiblingsTransaction::DoTransaction() {
    146  MOZ_LOG(GetLogModule(), LogLevel::Info,
    147          ("%p MoveSiblingsTransaction::%s this=%s", this, __FUNCTION__,
    148           ToString(*this).c_str()));
    149  mDone = true;
    150  return DoTransactionInternal();
    151 }
    152 
    153 nsresult MoveSiblingsTransaction::DoTransactionInternal() {
    154  MOZ_DIAGNOSTIC_ASSERT(mHTMLEditor);
    155  MOZ_DIAGNOSTIC_ASSERT(!mSiblingsToMove.IsEmpty());
    156  MOZ_DIAGNOSTIC_ASSERT(mContainer);
    157  MOZ_DIAGNOSTIC_ASSERT(mOldContainer);
    158 
    159  {
    160    const OwningNonNull<HTMLEditor> htmlEditor = *mHTMLEditor;
    161    const OwningNonNull<nsINode> newContainer = *mContainer;
    162    const nsCOMPtr<nsIContent> newNextSibling = mReference;
    163    const CopyableAutoTArray<OwningNonNull<nsIContent>, 64> siblingsToMove(
    164        mSiblingsToMove);
    165    AutoMoveNodeSelNotify notifier(
    166        htmlEditor->RangeUpdaterRef(),
    167        mReference ? EditorRawDOMPoint(mReference)
    168                   : EditorRawDOMPoint::AtEndOf(*newContainer));
    169    // First, remove all nodes from the DOM if they are removable.  Then,
    170    // IMEContentObserver can use cache to avoid to compute the start offset of
    171    // each deleting text.
    172    RemoveAllSiblingsToMove(htmlEditor, siblingsToMove, notifier);
    173    // Next, insert all removed nodes into the DOM.  Then, IMEContentObserver
    174    // can use cache to avoid to compute the start offset of each inserting
    175    // text.
    176    InsertAllSiblingsToMove(htmlEditor, siblingsToMove, newContainer,
    177                            newNextSibling, notifier);
    178  }
    179  return NS_WARN_IF(mHTMLEditor->Destroyed()) ? NS_ERROR_EDITOR_DESTROYED
    180                                              : NS_OK;
    181 }
    182 
    183 NS_IMETHODIMP MoveSiblingsTransaction::UndoTransaction() {
    184  MOZ_LOG(GetLogModule(), LogLevel::Info,
    185          ("%p MoveSiblingsTransaction::%s this=%s", this, __FUNCTION__,
    186           ToString(*this).c_str()));
    187 
    188  mDone = false;
    189 
    190  if (NS_WARN_IF(!mHTMLEditor) || NS_WARN_IF(mSiblingsToMove.IsEmpty()) ||
    191      NS_WARN_IF(!IsSiblingsToMoveValid()) || NS_WARN_IF(!mOldContainer)) {
    192    // Perhaps, nulled-out by the cycle collector.
    193    return NS_ERROR_FAILURE;
    194  }
    195 
    196  // If the original point has been changed, refer mOldNextSibling if it's
    197  // reasonable.  Otherwise, use end of the old container.
    198  if (mOldNextSibling && mOldContainer != mOldNextSibling->GetParentNode()) {
    199    // TODO: Check whether the new container is proper one for containing
    200    //       content in mSiblingsToMove.  However, there are few testcases so
    201    //       that we shouldn't change here without creating a lot of undo tests.
    202    if (mOldNextSibling->GetParentNode() &&
    203        (mOldNextSibling->IsInComposedDoc() ||
    204         !mOldContainer->IsInComposedDoc())) {
    205      mOldContainer = mOldNextSibling->GetParentNode();
    206    } else {
    207      mOldNextSibling = nullptr;  // end of mOldContainer
    208    }
    209  }
    210 
    211  if (MOZ_UNLIKELY(mOldContainer->IsInComposedDoc() &&
    212                   !HTMLEditUtils::IsSimplyEditableNode(*mOldContainer))) {
    213    NS_WARNING(
    214        "MoveSiblingsTransaction::UndoTransaction() couldn't move the "
    215        "content into the old container due to non-editable one");
    216    return NS_ERROR_FAILURE;
    217  }
    218 
    219  // And store the latest node which should be referred at redoing.
    220  mContainer = mSiblingsToMove.LastElement()->GetParentNode();
    221  mReference = mSiblingsToMove.LastElement()->GetNextSibling();
    222 
    223  {
    224    const OwningNonNull<HTMLEditor> htmlEditor = *mHTMLEditor;
    225    const OwningNonNull<nsINode> oldContainer = *mOldContainer;
    226    const nsCOMPtr<nsIContent> oldNextSibling = mOldNextSibling;
    227    const CopyableAutoTArray<OwningNonNull<nsIContent>, 64> siblingsToMove(
    228        mSiblingsToMove);
    229    AutoMoveNodeSelNotify notifier(
    230        htmlEditor->RangeUpdaterRef(),
    231        oldNextSibling ? EditorRawDOMPoint(oldNextSibling)
    232                       : EditorRawDOMPoint::AtEndOf(*oldContainer));
    233    // First, remove all nodes from the DOM if they are removable.  Then,
    234    // IMEContentObserver can use cache to avoid to compute the start offset of
    235    // each deleting text.
    236    RemoveAllSiblingsToMove(htmlEditor, siblingsToMove, notifier);
    237    // Next, insert all removed nodes into the DOM.  Then, IMEContentObserver
    238    // can use cache to avoid to compute the start offset of each inserting
    239    // text.
    240    InsertAllSiblingsToMove(htmlEditor, siblingsToMove, oldContainer,
    241                            oldNextSibling, notifier);
    242  }
    243 
    244  return NS_WARN_IF(mHTMLEditor->Destroyed()) ? NS_ERROR_EDITOR_DESTROYED
    245                                              : NS_OK;
    246 }
    247 
    248 NS_IMETHODIMP MoveSiblingsTransaction::RedoTransaction() {
    249  MOZ_LOG(GetLogModule(), LogLevel::Info,
    250          ("%p MoveSiblingsTransaction::%s this=%s", this, __FUNCTION__,
    251           ToString(*this).c_str()));
    252 
    253  mDone = true;
    254 
    255  if (NS_WARN_IF(!mHTMLEditor) || NS_WARN_IF(mSiblingsToMove.IsEmpty()) ||
    256      NS_WARN_IF(!IsSiblingsToMoveValid()) || NS_WARN_IF(!mContainer)) {
    257    // Perhaps, nulled-out by the cycle collector.
    258    return NS_ERROR_FAILURE;
    259  }
    260 
    261  // If the inserting point has been changed, refer mReference if it's
    262  // reasonable.  Otherwise, use end of the container.
    263  if (mReference && mContainer != mReference->GetParentNode()) {
    264    // TODO: Check whether the new container is proper one for containing
    265    //       mContentToMove.  However, there are few testcases so that we
    266    //       shouldn't change here without creating a lot of redo tests.
    267    if (mReference->GetParentNode() &&
    268        (mReference->IsInComposedDoc() || !mContainer->IsInComposedDoc())) {
    269      mContainer = mReference->GetParentNode();
    270    } else {
    271      mReference = nullptr;  // end of mContainer
    272    }
    273  }
    274 
    275  if (MOZ_UNLIKELY(mContainer->IsInComposedDoc() &&
    276                   !HTMLEditUtils::IsSimplyEditableNode(*mContainer))) {
    277    NS_WARNING(
    278        "MoveSiblingsTransaction::RedoTransaction() couldn't move the "
    279        "content into the new container due to non-editable one");
    280    return NS_ERROR_FAILURE;
    281  }
    282 
    283  // And store the latest node which should be back.
    284  mOldContainer = mSiblingsToMove.LastElement()->GetParentNode();
    285  mOldNextSibling = mSiblingsToMove.LastElement()->GetNextSibling();
    286 
    287  nsresult rv = DoTransactionInternal();
    288  if (NS_FAILED(rv)) {
    289    NS_WARNING("MoveSiblingsTransaction::DoTransactionInternal() failed");
    290    return rv;
    291  }
    292  return NS_OK;
    293 }
    294 
    295 nsIContent* MoveSiblingsTransaction::GetFirstMovedContent() const {
    296  nsINode* const expectedContainer = mDone ? mContainer : mOldContainer;
    297  for (const OwningNonNull<nsIContent>& content : mSiblingsToMove) {
    298    if (MOZ_LIKELY(content->GetParentNode() == expectedContainer)) {
    299      return content;
    300    }
    301  }
    302  return nullptr;
    303 }
    304 
    305 nsIContent* MoveSiblingsTransaction::GetLastMovedContent() const {
    306  nsINode* const expectedContainer = mDone ? mContainer : mOldContainer;
    307  for (const OwningNonNull<nsIContent>& content : Reversed(mSiblingsToMove)) {
    308    if (MOZ_LIKELY(content->GetParentNode() == expectedContainer)) {
    309      return content;
    310    }
    311  }
    312  return nullptr;
    313 }
    314 
    315 void MoveSiblingsTransaction::RemoveAllSiblingsToMove(
    316    HTMLEditor& aHTMLEditor,
    317    const nsTArray<OwningNonNull<nsIContent>>& aClonedSiblingsToMove,
    318    AutoMoveNodeSelNotify& aNotifier) const {
    319  // Be aware, if we're undoing or redoing, some aClonedSiblingsToMove may not
    320  // be the adjacent sibling of prev/next element in the array.  Therefore, we
    321  // may need to compute the index within the expensive path.
    322 
    323  // First, we need to make AutoMoveNodeSelNotify instances store all indices of
    324  // the moving content nodes.
    325  {
    326    for (const OwningNonNull<nsIContent>& contentToMove :
    327         aClonedSiblingsToMove) {
    328      if (contentToMove->IsInComposedDoc() &&
    329          !HTMLEditUtils::IsRemovableNode(contentToMove)) {
    330        continue;
    331      }
    332      aNotifier.AppendContentWhichWillBeMoved(contentToMove);
    333    }
    334  }
    335  // Then, remove all nodes unless not removable.
    336  for (const size_t i : IntegerRange(aNotifier.MovingContentCount())) {
    337    nsIContent* const contentToMove = aNotifier.GetContentAt(i);
    338    MOZ_ASSERT(contentToMove);
    339    AutoNodeAPIWrapper nodeWrapper(aHTMLEditor,
    340                                   // MOZ_KnownLive because it's guaranteed by
    341                                   // both notifier and aClonedSiblingsToMove.
    342                                   MOZ_KnownLive(*contentToMove));
    343    if (NS_FAILED(nodeWrapper.Remove())) {
    344      NS_WARNING("AutoNodeAPIWrapper::Remove() failed, but ignored");
    345    } else {
    346      NS_WARNING_ASSERTION(
    347          nodeWrapper.IsExpectedResult(),
    348          "Temporarily removing node caused other mutations, but ignored");
    349    }
    350  }
    351 }
    352 
    353 nsresult MoveSiblingsTransaction::InsertAllSiblingsToMove(
    354    HTMLEditor& aHTMLEditor,
    355    const nsTArray<OwningNonNull<nsIContent>>& aClonedSiblingsToMove,
    356    nsINode& aParentNode, nsIContent* aReferenceNode,
    357    AutoMoveNodeSelNotify& aNotifier) const {
    358  MOZ_ASSERT(mHTMLEditor);
    359  nsresult rv = NS_SUCCESS_DOM_NO_OPERATION;
    360  for (const size_t i : IntegerRange(aNotifier.MovingContentCount())) {
    361    nsIContent* const contentToMove = aNotifier.GetContentAt(i);
    362    MOZ_ASSERT(contentToMove);
    363    if (Element* const elementToMove = Element::FromNodeOrNull(contentToMove)) {
    364      if (!elementToMove->HasAttr(nsGkAtoms::mozdirty)) {
    365        nsresult rvMarkElementDirty = aHTMLEditor.MarkElementDirty(
    366            MOZ_KnownLive(*contentToMove->AsElement()));
    367        NS_WARNING_ASSERTION(
    368            NS_SUCCEEDED(rvMarkElementDirty),
    369            "EditorBase::MarkElementDirty() failed, but ignored");
    370        (void)rvMarkElementDirty;
    371      }
    372    }
    373 
    374    AutoNodeAPIWrapper nodeWrapper(aHTMLEditor, aParentNode);
    375    // MOZ_KnownLive because of guaranteed by both aNotifier and
    376    // aClonedSiblingsToMove.
    377    nsresult rvInner =
    378        nodeWrapper.InsertBefore(MOZ_KnownLive(*contentToMove), aReferenceNode);
    379    if (NS_FAILED(rvInner)) {
    380      NS_WARNING("AutoNodeAPIWrapper::InsertBefore() failed");
    381      rv = rvInner;
    382    } else {
    383      NS_WARNING_ASSERTION(nodeWrapper.IsExpectedResult(),
    384                           "Moving a node caused other mutations, but ignored");
    385    }
    386  }
    387 
    388  Document* const document = aHTMLEditor.GetDocument();
    389  for (const size_t i : IntegerRange(aNotifier.MovingContentCount())) {
    390    nsIContent* const content = aNotifier.GetContentAt(i);
    391    MOZ_ASSERT(content);
    392    if (MOZ_LIKELY(content->GetParentNode() &&
    393                   content->OwnerDoc() == document)) {
    394      aNotifier.DidMoveContent(*content);
    395    }
    396  }
    397  return NS_WARN_IF(aHTMLEditor.Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : rv;
    398 }
    399 
    400 }  // namespace mozilla