tor-browser

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

MoveNodeTransaction.cpp (13301B)


      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 /******************************************************************************
     28 * mozilla::MoveNodeTransactionBase
     29 ******************************************************************************/
     30 
     31 MoveNodeTransactionBase::MoveNodeTransactionBase(
     32    HTMLEditor& aHTMLEditor, nsIContent& aLastContentToMove,
     33    const EditorRawDOMPoint& aPointToInsert)
     34    : mContainer(aPointToInsert.GetContainer()),
     35      mReference(aPointToInsert.GetChild()),
     36      mOldContainer(aLastContentToMove.GetParentNode()),
     37      mOldNextSibling(aLastContentToMove.GetNextSibling()),
     38      mHTMLEditor(&aHTMLEditor) {
     39  MOZ_ASSERT(mContainer);
     40  MOZ_ASSERT(mOldContainer);
     41  MOZ_ASSERT_IF(mReference, mReference->GetParentNode() == mContainer);
     42  MOZ_ASSERT_IF(mOldNextSibling,
     43                mOldNextSibling->GetParentNode() == mOldContainer);
     44 }
     45 
     46 NS_IMPL_CYCLE_COLLECTION_INHERITED(MoveNodeTransactionBase, EditTransactionBase,
     47                                   mHTMLEditor, mContainer, mReference,
     48                                   mOldContainer, mOldNextSibling)
     49 
     50 NS_IMPL_ADDREF_INHERITED(MoveNodeTransactionBase, EditTransactionBase)
     51 NS_IMPL_RELEASE_INHERITED(MoveNodeTransactionBase, EditTransactionBase)
     52 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MoveNodeTransactionBase)
     53 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
     54 
     55 /******************************************************************************
     56 * mozilla::MoveNodeTransaction
     57 ******************************************************************************/
     58 
     59 template already_AddRefed<MoveNodeTransaction> MoveNodeTransaction::MaybeCreate(
     60    HTMLEditor& aHTMLEditor, nsIContent& aContentToMove,
     61    const EditorDOMPoint& aPointToInsert);
     62 template already_AddRefed<MoveNodeTransaction> MoveNodeTransaction::MaybeCreate(
     63    HTMLEditor& aHTMLEditor, nsIContent& aContentToMove,
     64    const EditorRawDOMPoint& aPointToInsert);
     65 
     66 // static
     67 template <typename PT, typename CT>
     68 already_AddRefed<MoveNodeTransaction> MoveNodeTransaction::MaybeCreate(
     69    HTMLEditor& aHTMLEditor, nsIContent& aContentToMove,
     70    const EditorDOMPointBase<PT, CT>& aPointToInsert) {
     71  if (NS_WARN_IF(!aContentToMove.GetParentNode()) ||
     72      NS_WARN_IF(!aPointToInsert.IsSet())) {
     73    return nullptr;
     74  }
     75  // TODO: We should not allow to move a node to improper container element.
     76  //       However, this is currently used to move invalid parent while
     77  //       processing the nodes.  Therefore, treating the case as error breaks
     78  //       a lot.
     79  if (NS_WARN_IF(aContentToMove.IsInComposedDoc() &&
     80                 !HTMLEditUtils::IsRemovableNode(aContentToMove)) ||
     81      // The destination should be editable, but it may be in an orphan node or
     82      // sub-tree to reduce number of DOM mutation events.  In such case, we're
     83      // okay to move a node into the non-editable content because we can assume
     84      // that the caller will insert it into an editable element.
     85      NS_WARN_IF(aPointToInsert.IsInComposedDoc() &&
     86                 !HTMLEditUtils::IsSimplyEditableNode(
     87                     *aPointToInsert.GetContainer()))) {
     88    return nullptr;
     89  }
     90  RefPtr<MoveNodeTransaction> transaction =
     91      new MoveNodeTransaction(aHTMLEditor, aContentToMove, aPointToInsert);
     92  return transaction.forget();
     93 }
     94 
     95 template <typename PT, typename CT>
     96 MoveNodeTransaction::MoveNodeTransaction(
     97    HTMLEditor& aHTMLEditor, nsIContent& aContentToMove,
     98    const EditorDOMPointBase<PT, CT>& aPointToInsert)
     99    : MoveNodeTransactionBase(aHTMLEditor, aContentToMove,
    100                              aPointToInsert.template To<EditorRawDOMPoint>()),
    101      mContentToMove(&aContentToMove) {}
    102 
    103 std::ostream& operator<<(std::ostream& aStream,
    104                         const MoveNodeTransaction& aTransaction) {
    105  auto DumpNodeDetails = [&](const nsINode* aNode) {
    106    if (aNode) {
    107      if (aNode->IsText()) {
    108        nsAutoString data;
    109        aNode->AsText()->GetData(data);
    110        aStream << " (#text \"" << NS_ConvertUTF16toUTF8(data).get() << "\")";
    111      } else {
    112        aStream << " (" << *aNode << ")";
    113      }
    114    }
    115  };
    116  aStream << "{ mContentToMove=" << aTransaction.mContentToMove.get();
    117  DumpNodeDetails(aTransaction.mContentToMove);
    118  aStream << ", mContainer=" << aTransaction.mContainer.get();
    119  DumpNodeDetails(aTransaction.mContainer);
    120  aStream << ", mReference=" << aTransaction.mReference.get();
    121  DumpNodeDetails(aTransaction.mReference);
    122  aStream << ", mOldContainer=" << aTransaction.mOldContainer.get();
    123  DumpNodeDetails(aTransaction.mOldContainer);
    124  aStream << ", mOldNextSibling=" << aTransaction.mOldNextSibling.get();
    125  DumpNodeDetails(aTransaction.mOldNextSibling);
    126  aStream << ", mHTMLEditor=" << aTransaction.mHTMLEditor.get() << " }";
    127  return aStream;
    128 }
    129 
    130 NS_IMPL_CYCLE_COLLECTION_INHERITED(MoveNodeTransaction, MoveNodeTransactionBase,
    131                                   mContentToMove)
    132 
    133 NS_IMPL_ADDREF_INHERITED(MoveNodeTransaction, MoveNodeTransactionBase)
    134 NS_IMPL_RELEASE_INHERITED(MoveNodeTransaction, MoveNodeTransactionBase)
    135 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MoveNodeTransaction)
    136 NS_INTERFACE_MAP_END_INHERITING(MoveNodeTransactionBase)
    137 
    138 NS_IMETHODIMP MoveNodeTransaction::DoTransaction() {
    139  MOZ_LOG(GetLogModule(), LogLevel::Info,
    140          ("%p MoveNodeTransaction::%s this=%s", this, __FUNCTION__,
    141           ToString(*this).c_str()));
    142  return DoTransactionInternal();
    143 }
    144 
    145 nsresult MoveNodeTransaction::DoTransactionInternal() {
    146  MOZ_DIAGNOSTIC_ASSERT(mHTMLEditor);
    147  MOZ_DIAGNOSTIC_ASSERT(mContentToMove);
    148  MOZ_DIAGNOSTIC_ASSERT(mContainer);
    149  MOZ_DIAGNOSTIC_ASSERT(mOldContainer);
    150 
    151  const OwningNonNull<HTMLEditor> htmlEditor = *mHTMLEditor;
    152  const OwningNonNull<nsIContent> contentToMove = *mContentToMove;
    153  const OwningNonNull<nsINode> container = *mContainer;
    154  const nsCOMPtr<nsIContent> newNextSibling = mReference;
    155  if (contentToMove->IsElement()) {
    156    nsresult rv = htmlEditor->MarkElementDirty(
    157        MOZ_KnownLive(*contentToMove->AsElement()));
    158    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
    159      return EditorBase::ToGenericNSResult(rv);
    160    }
    161    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    162                         "EditorBase::MarkElementDirty() failed, but ignored");
    163  }
    164 
    165  {
    166    AutoMoveNodeSelNotify notifyStoredRanges(
    167        htmlEditor->RangeUpdaterRef(), contentToMove,
    168        newNextSibling ? EditorRawDOMPoint(newNextSibling)
    169                       : EditorRawDOMPoint::AtEndOf(*container));
    170    AutoNodeAPIWrapper nodeWrapper(htmlEditor, container);
    171    nsresult rv = nodeWrapper.InsertBefore(contentToMove, newNextSibling);
    172    if (NS_FAILED(rv)) {
    173      NS_WARNING("AutoNodeAPIWrapper::InsertBefore() failed");
    174      return rv;
    175    }
    176    NS_WARNING_ASSERTION(nodeWrapper.IsExpectedResult(),
    177                         "Moving node caused other mutations, but ignored");
    178    if (MOZ_LIKELY(contentToMove->GetParentNode() &&
    179                   contentToMove->OwnerDoc() == htmlEditor->GetDocument())) {
    180      notifyStoredRanges.DidMoveContent(contentToMove);
    181    }
    182  }
    183 
    184  return NS_OK;
    185 }
    186 
    187 NS_IMETHODIMP MoveNodeTransaction::UndoTransaction() {
    188  MOZ_LOG(GetLogModule(), LogLevel::Info,
    189          ("%p MoveNodeTransaction::%s this=%s", this, __FUNCTION__,
    190           ToString(*this).c_str()));
    191 
    192  if (NS_WARN_IF(!mHTMLEditor) || NS_WARN_IF(!mContentToMove) ||
    193      NS_WARN_IF(!mOldContainer)) {
    194    // Perhaps, nulled-out by the cycle collector.
    195    return NS_ERROR_FAILURE;
    196  }
    197 
    198  // If the original point has been changed, refer mOldNextSibling if it's
    199  // reasonable.  Otherwise, use end of the old container.
    200  if (mOldNextSibling && mOldContainer != mOldNextSibling->GetParentNode()) {
    201    // TODO: Check whether the new container is proper one for containing
    202    //       mContentToMove.  However, there are few testcases so that we
    203    //       shouldn't change here without creating a lot of undo tests.
    204    if (mOldNextSibling->GetParentNode() &&
    205        (mOldNextSibling->IsInComposedDoc() ||
    206         !mOldContainer->IsInComposedDoc())) {
    207      mOldContainer = mOldNextSibling->GetParentNode();
    208    } else {
    209      mOldNextSibling = nullptr;  // end of mOldContainer
    210    }
    211  }
    212 
    213  if (MOZ_UNLIKELY(mOldContainer->IsInComposedDoc() &&
    214                   !HTMLEditUtils::IsSimplyEditableNode(*mOldContainer))) {
    215    NS_WARNING(
    216        "MoveNodeTransaction::UndoTransaction() couldn't move the "
    217        "content into the old container due to non-editable one");
    218    return NS_ERROR_FAILURE;
    219  }
    220  if (MOZ_UNLIKELY(mContentToMove->IsInComposedDoc() &&
    221                   !HTMLEditUtils::IsRemovableNode(*mContentToMove))) {
    222    // For the consistency with MoveSiblingsTransaction::UndoTransaction(), we
    223    // should return "OK" even if we cannot move the node from non-editable
    224    // node.
    225    return NS_OK;
    226  }
    227 
    228  // And store the latest node which should be referred at redoing.
    229  mContainer = mContentToMove->GetParentNode();
    230  mReference = mContentToMove->GetNextSibling();
    231 
    232  const OwningNonNull<HTMLEditor> htmlEditor = *mHTMLEditor;
    233  const OwningNonNull<nsINode> oldContainer = *mOldContainer;
    234  const OwningNonNull<nsIContent> contentToMove = *mContentToMove;
    235  const nsCOMPtr<nsIContent> oldNextSibling = mOldNextSibling;
    236  if (contentToMove->IsElement()) {
    237    nsresult rv = htmlEditor->MarkElementDirty(
    238        MOZ_KnownLive(*contentToMove->AsElement()));
    239    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
    240      return EditorBase::ToGenericNSResult(rv);
    241    }
    242    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    243                         "EditorBase::MarkElementDirty() failed, but ignored");
    244  }
    245 
    246  {
    247    AutoMoveNodeSelNotify notifyStoredRanges(
    248        htmlEditor->RangeUpdaterRef(), contentToMove,
    249        oldNextSibling ? EditorRawDOMPoint(oldNextSibling)
    250                       : EditorRawDOMPoint::AtEndOf(*oldContainer));
    251    AutoNodeAPIWrapper nodeWrapper(htmlEditor, oldContainer);
    252    nsresult rv = nodeWrapper.InsertBefore(contentToMove, oldNextSibling);
    253    if (NS_FAILED(rv)) {
    254      NS_WARNING("AutoNodeAPIWrapper::InsertBefore() failed");
    255      return rv;
    256    }
    257    NS_WARNING_ASSERTION(nodeWrapper.IsExpectedResult(),
    258                         "Moving node caused other mutations, but ignored");
    259    if (MOZ_LIKELY(contentToMove->GetParentNode() &&
    260                   contentToMove->OwnerDoc() == htmlEditor->GetDocument())) {
    261      notifyStoredRanges.DidMoveContent(contentToMove);
    262    }
    263  }
    264 
    265  return NS_OK;
    266 }
    267 
    268 NS_IMETHODIMP MoveNodeTransaction::RedoTransaction() {
    269  MOZ_LOG(GetLogModule(), LogLevel::Info,
    270          ("%p MoveNodeTransaction::%s this=%s", this, __FUNCTION__,
    271           ToString(*this).c_str()));
    272 
    273  if (NS_WARN_IF(!mHTMLEditor) || NS_WARN_IF(!mContentToMove) ||
    274      NS_WARN_IF(!mContainer)) {
    275    // Perhaps, nulled-out by the cycle collector.
    276    return NS_ERROR_FAILURE;
    277  }
    278 
    279  // If the inserting point has been changed, refer mReference if it's
    280  // reasonable.  Otherwise, use end of the container.
    281  if (mReference && mContainer != mReference->GetParentNode()) {
    282    // TODO: Check whether the new container is proper one for containing
    283    //       mContentToMove.  However, there are few testcases so that we
    284    //       shouldn't change here without creating a lot of redo tests.
    285    if (mReference->GetParentNode() &&
    286        (mReference->IsInComposedDoc() || !mContainer->IsInComposedDoc())) {
    287      mContainer = mReference->GetParentNode();
    288    } else {
    289      mReference = nullptr;  // end of mContainer
    290    }
    291  }
    292 
    293  if (MOZ_UNLIKELY(mContainer->IsInComposedDoc() &&
    294                   !HTMLEditUtils::IsSimplyEditableNode(*mContainer))) {
    295    NS_WARNING(
    296        "MoveNodeTransaction::RedoTransaction() couldn't move the "
    297        "content into the new container due to non-editable one");
    298    return NS_ERROR_FAILURE;
    299  }
    300  if (NS_WARN_IF(mContentToMove->IsInComposedDoc() &&
    301                 !HTMLEditUtils::IsRemovableNode(*mContentToMove))) {
    302    // For the consistency with MoveSiblingsTransaction::RedoTransaction(), we
    303    // should return "OK" even if we cannot move the node from non-editable
    304    // node.
    305    return NS_OK;
    306  }
    307 
    308  // And store the latest node which should be back.
    309  mOldContainer = mContentToMove->GetParentNode();
    310  mOldNextSibling = mContentToMove->GetNextSibling();
    311 
    312  nsresult rv = DoTransactionInternal();
    313  if (NS_FAILED(rv)) {
    314    NS_WARNING("MoveNodeTransaction::DoTransactionInternal() failed");
    315    return rv;
    316  }
    317  return NS_OK;
    318 }
    319 
    320 }  // namespace mozilla