tor-browser

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

SplitNodeTransaction.cpp (9136B)


      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 "SplitNodeTransaction.h"
      7 
      8 #include "EditorDOMPoint.h"   // for EditorRawDOMPoint
      9 #include "HTMLEditHelpers.h"  // for SplitNodeResult
     10 #include "HTMLEditor.h"       // for HTMLEditor
     11 #include "HTMLEditorInlines.h"
     12 #include "HTMLEditUtils.h"
     13 #include "SelectionState.h"  // for AutoTrackDOMPoint and RangeUpdater
     14 
     15 #include "mozilla/Logging.h"
     16 #include "mozilla/ToString.h"
     17 #include "nsAString.h"
     18 #include "nsDebug.h"     // for NS_ASSERTION, etc.
     19 #include "nsError.h"     // for NS_ERROR_NOT_INITIALIZED, etc.
     20 #include "nsIContent.h"  // for nsIContent
     21 
     22 namespace mozilla {
     23 
     24 using namespace dom;
     25 
     26 template already_AddRefed<SplitNodeTransaction> SplitNodeTransaction::Create(
     27    HTMLEditor& aHTMLEditor, const EditorDOMPoint& aStartOfRightContent);
     28 template already_AddRefed<SplitNodeTransaction> SplitNodeTransaction::Create(
     29    HTMLEditor& aHTMLEditor, const EditorRawDOMPoint& aStartOfRightContent);
     30 
     31 // static
     32 template <typename PT, typename CT>
     33 already_AddRefed<SplitNodeTransaction> SplitNodeTransaction::Create(
     34    HTMLEditor& aHTMLEditor,
     35    const EditorDOMPointBase<PT, CT>& aStartOfRightContent) {
     36  RefPtr<SplitNodeTransaction> transaction =
     37      new SplitNodeTransaction(aHTMLEditor, aStartOfRightContent);
     38  return transaction.forget();
     39 }
     40 
     41 template <typename PT, typename CT>
     42 SplitNodeTransaction::SplitNodeTransaction(
     43    HTMLEditor& aHTMLEditor,
     44    const EditorDOMPointBase<PT, CT>& aStartOfRightContent)
     45    : mHTMLEditor(&aHTMLEditor),
     46      mSplitContent(aStartOfRightContent.template GetContainerAs<nsIContent>()),
     47      mSplitOffset(aStartOfRightContent.Offset()) {
     48  // printf("SplitNodeTransaction size: %zu\n", sizeof(SplitNodeTransaction));
     49  static_assert(sizeof(SplitNodeTransaction) <= 64,
     50                "Transaction classes may be created a lot and may be alive "
     51                "long so that keep the foot print smaller as far as possible");
     52  MOZ_DIAGNOSTIC_ASSERT(aStartOfRightContent.IsInContentNode());
     53  MOZ_DIAGNOSTIC_ASSERT(HTMLEditUtils::IsSplittableNode(
     54      *aStartOfRightContent.template ContainerAs<nsIContent>()));
     55 }
     56 
     57 std::ostream& operator<<(std::ostream& aStream,
     58                         const SplitNodeTransaction& aTransaction) {
     59  aStream << "{ mParentNode=" << aTransaction.mParentNode.get();
     60  if (aTransaction.mParentNode) {
     61    aStream << " (" << *aTransaction.mParentNode << ")";
     62  }
     63  aStream << ", mNewContent=" << aTransaction.mNewContent.get();
     64  if (aTransaction.mNewContent) {
     65    aStream << " (" << *aTransaction.mNewContent << ")";
     66  }
     67  aStream << ", mSplitContent=" << aTransaction.mSplitContent.get();
     68  if (aTransaction.mSplitContent) {
     69    aStream << " (" << *aTransaction.mSplitContent << ")";
     70  }
     71  aStream << ", mSplitOffset=" << aTransaction.mSplitOffset
     72          << ", mHTMLEditor=" << aTransaction.mHTMLEditor.get() << " }";
     73  return aStream;
     74 }
     75 
     76 NS_IMPL_CYCLE_COLLECTION_INHERITED(SplitNodeTransaction, EditTransactionBase,
     77                                   mHTMLEditor, mParentNode, mSplitContent,
     78                                   mNewContent)
     79 
     80 NS_IMPL_ADDREF_INHERITED(SplitNodeTransaction, EditTransactionBase)
     81 NS_IMPL_RELEASE_INHERITED(SplitNodeTransaction, EditTransactionBase)
     82 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SplitNodeTransaction)
     83 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
     84 
     85 NS_IMETHODIMP SplitNodeTransaction::DoTransaction() {
     86  MOZ_LOG(GetLogModule(), LogLevel::Info,
     87          ("%p SplitNodeTransaction::%s this=%s", this, __FUNCTION__,
     88           ToString(*this).c_str()));
     89 
     90  if (MOZ_UNLIKELY(NS_WARN_IF(!mHTMLEditor) || NS_WARN_IF(!mSplitContent))) {
     91    return NS_ERROR_NOT_AVAILABLE;
     92  }
     93  MOZ_ASSERT(mSplitOffset <= mSplitContent->Length());
     94 
     95  // Create a new node
     96  IgnoredErrorResult error;
     97  // Don't use .downcast directly because AsContent has an assertion we want
     98  nsCOMPtr<nsINode> newNode = mSplitContent->CloneNode(false, error);
     99  if (MOZ_UNLIKELY(error.Failed())) {
    100    NS_WARNING("nsINode::CloneNode() failed");
    101    return error.StealNSResult();
    102  }
    103  if (MOZ_UNLIKELY(NS_WARN_IF(!newNode))) {
    104    return NS_ERROR_UNEXPECTED;
    105  }
    106 
    107  mNewContent = newNode->AsContent();
    108  mParentNode = mSplitContent->GetParentNode();
    109  if (!mParentNode) {
    110    NS_WARNING("The splitting content was an orphan node");
    111    return NS_ERROR_NOT_AVAILABLE;
    112  }
    113 
    114  const OwningNonNull<HTMLEditor> htmlEditor = *mHTMLEditor;
    115  const OwningNonNull<nsIContent> splittingContent = *mSplitContent;
    116  // MOZ_KnownLive(*mNewContent): it's grabbed by newNode
    117  Result<SplitNodeResult, nsresult> splitNodeResult = DoTransactionInternal(
    118      htmlEditor, splittingContent, MOZ_KnownLive(*mNewContent), mSplitOffset);
    119  if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
    120    NS_WARNING("SplitNodeTransaction::DoTransactionInternal() failed");
    121    return EditorBase::ToGenericNSResult(splitNodeResult.unwrapErr());
    122  }
    123  // The user should handle selection rather here.
    124  splitNodeResult.inspect().IgnoreCaretPointSuggestion();
    125  return NS_OK;
    126 }
    127 
    128 Result<SplitNodeResult, nsresult> SplitNodeTransaction::DoTransactionInternal(
    129    HTMLEditor& aHTMLEditor, nsIContent& aSplittingContent,
    130    nsIContent& aNewContent, uint32_t aSplitOffset) {
    131  if (Element* const splittingElement = Element::FromNode(aSplittingContent)) {
    132    // MOZ_KnownLive(*splittingElement): aSplittingContent should be grabbed by
    133    // the callers.
    134    nsresult rv =
    135        aHTMLEditor.MarkElementDirty(MOZ_KnownLive(*splittingElement));
    136    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
    137      return Err(NS_ERROR_EDITOR_DESTROYED);
    138    }
    139    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    140                         "EditorBase::MarkElementDirty() failed, but ignored");
    141  }
    142 
    143  Result<SplitNodeResult, nsresult> splitNodeResult = aHTMLEditor.DoSplitNode(
    144      EditorDOMPoint(&aSplittingContent,
    145                     std::min(aSplitOffset, aSplittingContent.Length())),
    146      aNewContent);
    147  if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
    148    NS_WARNING("HTMLEditor::DoSplitNode() failed");
    149    return splitNodeResult;
    150  }
    151  // When adding caret suggestion to SplitNodeResult, here didn't change
    152  // selection so that just ignore it.
    153  splitNodeResult.inspect().IgnoreCaretPointSuggestion();
    154  return splitNodeResult;
    155 }
    156 
    157 NS_IMETHODIMP SplitNodeTransaction::UndoTransaction() {
    158  MOZ_LOG(GetLogModule(), LogLevel::Info,
    159          ("%p SplitNodeTransaction::%s this=%s", this, __FUNCTION__,
    160           ToString(*this).c_str()));
    161 
    162  if (MOZ_UNLIKELY(NS_WARN_IF(!mHTMLEditor) || NS_WARN_IF(!mNewContent) ||
    163                   NS_WARN_IF(!mParentNode) || NS_WARN_IF(!mSplitContent) ||
    164                   NS_WARN_IF(mNewContent->IsBeingRemoved()))) {
    165    return NS_ERROR_NOT_AVAILABLE;
    166  }
    167 
    168  // This assumes Do inserted the new node in front of the prior existing node
    169  const OwningNonNull<HTMLEditor> htmlEditor = *mHTMLEditor;
    170  const OwningNonNull<nsIContent> keepingContent = *mSplitContent;
    171  const OwningNonNull<nsIContent> removingContent = *mNewContent;
    172  EditorDOMPoint joinedPoint;
    173  // Unfortunately, we cannot track joining point if moving right node content
    174  // into left node since it cannot track changes from web apps and HTMLEditor
    175  // never removes the content of the left node.  So it should be true that
    176  // we don't need to track the point in this case.
    177  nsresult rv = htmlEditor->DoJoinNodes(keepingContent, removingContent);
    178  if (NS_SUCCEEDED(rv)) {
    179    // Adjust split offset for redo here
    180    if (joinedPoint.IsSet()) {
    181      mSplitOffset = joinedPoint.Offset();
    182    }
    183  } else {
    184    NS_WARNING("HTMLEditor::DoJoinNodes() failed");
    185  }
    186  return rv;
    187 }
    188 
    189 /* Redo cannot simply resplit the right node, because subsequent transactions
    190 * on the redo stack may depend on the left node existing in its previous
    191 * state.
    192 */
    193 NS_IMETHODIMP SplitNodeTransaction::RedoTransaction() {
    194  MOZ_LOG(GetLogModule(), LogLevel::Info,
    195          ("%p SplitNodeTransaction::%s this=%s", this, __FUNCTION__,
    196           ToString(*this).c_str()));
    197 
    198  if (MOZ_UNLIKELY(NS_WARN_IF(!mNewContent) || NS_WARN_IF(!mParentNode) ||
    199                   NS_WARN_IF(!mSplitContent) || NS_WARN_IF(!mHTMLEditor))) {
    200    return NS_ERROR_NOT_AVAILABLE;
    201  }
    202 
    203  const OwningNonNull<HTMLEditor> htmlEditor = *mHTMLEditor;
    204  const OwningNonNull<nsIContent> newContent = *mNewContent;
    205  const OwningNonNull<nsIContent> splittingContent = *mSplitContent;
    206  Result<SplitNodeResult, nsresult> splitNodeResult = DoTransactionInternal(
    207      htmlEditor, splittingContent, newContent, mSplitOffset);
    208  if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
    209    NS_WARNING("SplitNodeTransaction::DoTransactionInternal() failed");
    210    return EditorBase::ToGenericNSResult(splitNodeResult.unwrapErr());
    211  }
    212  // When adding caret suggestion to SplitNodeResult, here didn't change
    213  // selection so that just ignore it.
    214  splitNodeResult.inspect().IgnoreCaretPointSuggestion();
    215  return NS_OK;
    216 }
    217 
    218 }  // namespace mozilla