diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /editor/libeditor/MoveNodeTransaction.cpp | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'editor/libeditor/MoveNodeTransaction.cpp')
-rw-r--r-- | editor/libeditor/MoveNodeTransaction.cpp | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/editor/libeditor/MoveNodeTransaction.cpp b/editor/libeditor/MoveNodeTransaction.cpp new file mode 100644 index 0000000000..a64e5d5ec0 --- /dev/null +++ b/editor/libeditor/MoveNodeTransaction.cpp @@ -0,0 +1,300 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MoveNodeTransaction.h" + +#include "EditorBase.h" // for EditorBase +#include "EditorDOMPoint.h" // for EditorDOMPoint +#include "HTMLEditor.h" // for HTMLEditor +#include "HTMLEditUtils.h" // for HTMLEditUtils + +#include "mozilla/Likely.h" +#include "mozilla/Logging.h" +#include "mozilla/ToString.h" + +#include "nsDebug.h" // for NS_WARNING, etc. +#include "nsError.h" // for NS_ERROR_NULL_POINTER, etc. +#include "nsIContent.h" // for nsIContent +#include "nsString.h" // for nsString + +namespace mozilla { + +using namespace dom; + +template already_AddRefed<MoveNodeTransaction> MoveNodeTransaction::MaybeCreate( + HTMLEditor& aHTMLEditor, nsIContent& aContentToMove, + const EditorDOMPoint& aPointToInsert); +template already_AddRefed<MoveNodeTransaction> MoveNodeTransaction::MaybeCreate( + HTMLEditor& aHTMLEditor, nsIContent& aContentToMove, + const EditorRawDOMPoint& aPointToInsert); + +// static +template <typename PT, typename CT> +already_AddRefed<MoveNodeTransaction> MoveNodeTransaction::MaybeCreate( + HTMLEditor& aHTMLEditor, nsIContent& aContentToMove, + const EditorDOMPointBase<PT, CT>& aPointToInsert) { + if (NS_WARN_IF(!aContentToMove.GetParentNode()) || + NS_WARN_IF(!aPointToInsert.IsSet())) { + return nullptr; + } + // TODO: We should not allow to move a node to improper container element. + // However, this is currently used to move invalid parent while + // processing the nodes. Therefore, treating the case as error breaks + // a lot. + if (NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(aContentToMove)) || + // The destination should be editable, but it may be in an orphan node or + // sub-tree to reduce number of DOM mutation events. In such case, we're + // okay to move a node into the non-editable content because we can assume + // that the caller will insert it into an editable element. + NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode( + *aPointToInsert.GetContainer()) && + aPointToInsert.GetContainer()->IsInComposedDoc())) { + return nullptr; + } + RefPtr<MoveNodeTransaction> transaction = + new MoveNodeTransaction(aHTMLEditor, aContentToMove, aPointToInsert); + return transaction.forget(); +} + +template <typename PT, typename CT> +MoveNodeTransaction::MoveNodeTransaction( + HTMLEditor& aHTMLEditor, nsIContent& aContentToMove, + const EditorDOMPointBase<PT, CT>& aPointToInsert) + : mContentToMove(&aContentToMove), + mContainer(aPointToInsert.GetContainer()), + mReference(aPointToInsert.GetChild()), + mOldContainer(aContentToMove.GetParentNode()), + mOldNextSibling(aContentToMove.GetNextSibling()), + mHTMLEditor(&aHTMLEditor) { + MOZ_ASSERT(mContainer); + MOZ_ASSERT(mOldContainer); + MOZ_ASSERT_IF(mReference, mReference->GetParentNode() == mContainer); + MOZ_ASSERT_IF(mOldNextSibling, + mOldNextSibling->GetParentNode() == mOldContainer); + // printf("MoveNodeTransaction size: %zu\n", sizeof(MoveNodeTransaction)); + static_assert(sizeof(MoveNodeTransaction) <= 72, + "Transaction classes may be created a lot and may be alive " + "long so that keep the foot print smaller as far as possible"); +} + +std::ostream& operator<<(std::ostream& aStream, + const MoveNodeTransaction& aTransaction) { + auto DumpNodeDetails = [&](const nsINode* aNode) { + if (aNode) { + if (aNode->IsText()) { + nsAutoString data; + aNode->AsText()->GetData(data); + aStream << " (#text \"" << NS_ConvertUTF16toUTF8(data).get() << "\")"; + } else { + aStream << " (" << *aNode << ")"; + } + } + }; + aStream << "{ mContentToMove=" << aTransaction.mContentToMove.get(); + DumpNodeDetails(aTransaction.mContentToMove); + aStream << ", mContainer=" << aTransaction.mContainer.get(); + DumpNodeDetails(aTransaction.mContainer); + aStream << ", mReference=" << aTransaction.mReference.get(); + DumpNodeDetails(aTransaction.mReference); + aStream << ", mOldContainer=" << aTransaction.mOldContainer.get(); + DumpNodeDetails(aTransaction.mOldContainer); + aStream << ", mOldNextSibling=" << aTransaction.mOldNextSibling.get(); + DumpNodeDetails(aTransaction.mOldNextSibling); + aStream << ", mHTMLEditor=" << aTransaction.mHTMLEditor.get() << " }"; + return aStream; +} + +NS_IMPL_CYCLE_COLLECTION_INHERITED(MoveNodeTransaction, EditTransactionBase, + mHTMLEditor, mContentToMove, mContainer, + mReference, mOldContainer, mOldNextSibling) + +NS_IMPL_ADDREF_INHERITED(MoveNodeTransaction, EditTransactionBase) +NS_IMPL_RELEASE_INHERITED(MoveNodeTransaction, EditTransactionBase) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MoveNodeTransaction) +NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase) + +NS_IMETHODIMP MoveNodeTransaction::DoTransaction() { + MOZ_LOG(GetLogModule(), LogLevel::Info, + ("%p MoveNodeTransaction::%s this=%s", this, __FUNCTION__, + ToString(*this).c_str())); + return DoTransactionInternal(); +} + +nsresult MoveNodeTransaction::DoTransactionInternal() { + MOZ_DIAGNOSTIC_ASSERT(mHTMLEditor); + MOZ_DIAGNOSTIC_ASSERT(mContentToMove); + MOZ_DIAGNOSTIC_ASSERT(mContainer); + MOZ_DIAGNOSTIC_ASSERT(mOldContainer); + + OwningNonNull<HTMLEditor> htmlEditor = *mHTMLEditor; + OwningNonNull<nsIContent> contentToMove = *mContentToMove; + OwningNonNull<nsINode> container = *mContainer; + nsCOMPtr<nsIContent> newNextSibling = mReference; + if (contentToMove->IsElement()) { + nsresult rv = htmlEditor->MarkElementDirty( + MOZ_KnownLive(*contentToMove->AsElement())); + if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { + return EditorBase::ToGenericNSResult(rv); + } + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "EditorBase::MarkElementDirty() failed, but ignored"); + } + + { + AutoMoveNodeSelNotify notifyStoredRanges( + htmlEditor->RangeUpdaterRef(), EditorRawDOMPoint(contentToMove), + newNextSibling ? EditorRawDOMPoint(newNextSibling) + : EditorRawDOMPoint::AtEndOf(container)); + IgnoredErrorResult error; + container->InsertBefore(contentToMove, newNextSibling, error); + // InsertBefore() may call MightThrowJSException() even if there is no + // error. We don't need the flag here. + error.WouldReportJSException(); + if (error.Failed()) { + NS_WARNING("nsINode::InsertBefore() failed"); + return error.StealNSResult(); + } + } + + return NS_OK; +} + +NS_IMETHODIMP MoveNodeTransaction::UndoTransaction() { + MOZ_LOG(GetLogModule(), LogLevel::Info, + ("%p MoveNodeTransaction::%s this=%s", this, __FUNCTION__, + ToString(*this).c_str())); + + if (NS_WARN_IF(!mHTMLEditor) || NS_WARN_IF(!mContentToMove) || + NS_WARN_IF(!mOldContainer)) { + // Perhaps, nulled-out by the cycle collector. + return NS_ERROR_FAILURE; + } + + // If the original point has been changed, refer mOldNextSibling if it's + // renasonable. Otherwise, use end of the old container. + if (mOldNextSibling && mOldContainer != mOldNextSibling->GetParentNode()) { + // TODO: Check whether the new container is proper one for containing + // mContentToMove. However, there are few testcases so that we + // shouldn't change here without creating a lot of undo tests. + if (mOldNextSibling->GetParentNode() && + (mOldNextSibling->IsInComposedDoc() || + !mOldContainer->IsInComposedDoc())) { + mOldContainer = mOldNextSibling->GetParentNode(); + } else { + mOldNextSibling = nullptr; // end of mOldContainer + } + } + + if (MOZ_UNLIKELY(!HTMLEditUtils::IsRemovableNode(*mContentToMove))) { + NS_WARNING( + "MoveNodeTransaction::UndoTransaction() couldn't move the " + "content due to not removable from its current container"); + return NS_ERROR_FAILURE; + } + if (MOZ_UNLIKELY(!HTMLEditUtils::IsSimplyEditableNode(*mOldContainer))) { + NS_WARNING( + "MoveNodeTransaction::UndoTransaction() couldn't move the " + "content into the old container due to non-editable one"); + return NS_ERROR_FAILURE; + } + + // And store the latest node which should be referred at redoing. + mContainer = mContentToMove->GetParentNode(); + mReference = mContentToMove->GetNextSibling(); + + OwningNonNull<HTMLEditor> htmlEditor = *mHTMLEditor; + OwningNonNull<nsINode> oldContainer = *mOldContainer; + OwningNonNull<nsIContent> contentToMove = *mContentToMove; + nsCOMPtr<nsIContent> oldNextSibling = mOldNextSibling; + if (contentToMove->IsElement()) { + nsresult rv = htmlEditor->MarkElementDirty( + MOZ_KnownLive(*contentToMove->AsElement())); + if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { + return EditorBase::ToGenericNSResult(rv); + } + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "EditorBase::MarkElementDirty() failed, but ignored"); + } + + { + AutoMoveNodeSelNotify notifyStoredRanges( + htmlEditor->RangeUpdaterRef(), EditorRawDOMPoint(contentToMove), + oldNextSibling ? EditorRawDOMPoint(oldNextSibling) + : EditorRawDOMPoint::AtEndOf(oldContainer)); + IgnoredErrorResult error; + oldContainer->InsertBefore(contentToMove, oldNextSibling, error); + // InsertBefore() may call MightThrowJSException() even if there is no + // error. We don't need the flag here. + error.WouldReportJSException(); + if (error.Failed()) { + NS_WARNING("nsINode::InsertBefore() failed"); + return error.StealNSResult(); + } + } + + return NS_OK; +} + +NS_IMETHODIMP MoveNodeTransaction::RedoTransaction() { + MOZ_LOG(GetLogModule(), LogLevel::Info, + ("%p MoveNodeTransaction::%s this=%s", this, __FUNCTION__, + ToString(*this).c_str())); + + if (NS_WARN_IF(!mHTMLEditor) || NS_WARN_IF(!mContentToMove) || + NS_WARN_IF(!mContainer)) { + // Perhaps, nulled-out by the cycle collector. + return NS_ERROR_FAILURE; + } + + // If the inserting point has been changed, refer mReference if it's + // renasonable. Otherwise, use end of the container. + if (mReference && mContainer != mReference->GetParentNode()) { + // TODO: Check whether the new container is proper one for containing + // mContentToMove. However, there are few testcases so that we + // shouldn't change here without creating a lot of redo tests. + if (mReference->GetParentNode() && + (mReference->IsInComposedDoc() || !mContainer->IsInComposedDoc())) { + mContainer = mReference->GetParentNode(); + } else { + mReference = nullptr; // end of mContainer + } + } + + if (MOZ_UNLIKELY(!HTMLEditUtils::IsRemovableNode(*mContentToMove))) { + NS_WARNING( + "MoveNodeTransaction::RedoTransaction() couldn't move the " + "content due to not removable from its current container"); + return NS_ERROR_FAILURE; + } + if (MOZ_UNLIKELY(!HTMLEditUtils::IsSimplyEditableNode(*mContainer))) { + NS_WARNING( + "MoveNodeTransaction::RedoTransaction() couldn't move the " + "content into the new container due to non-editable one"); + return NS_ERROR_FAILURE; + } + + // And store the latest node which should be back. + mOldContainer = mContentToMove->GetParentNode(); + mOldNextSibling = mContentToMove->GetNextSibling(); + + nsresult rv = DoTransactionInternal(); + if (NS_FAILED(rv)) { + NS_WARNING("MoveNodeTransaction::DoTransactionInternal() failed"); + return rv; + } + + if (!mHTMLEditor->AllowsTransactionsToChangeSelection()) { + return NS_OK; + } + + OwningNonNull<HTMLEditor> htmlEditor(*mHTMLEditor); + rv = htmlEditor->CollapseSelectionTo( + SuggestPointToPutCaret<EditorRawDOMPoint>()); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "EditorBase::CollapseSelectionTo() failed, but ignored"); + return NS_OK; +} + +} // namespace mozilla |