Bug 1766355 - part 1: Add `MoveNodeTransaction` to handle delete node and insert node in a transaction class instance r=m_kato

Creating both `DeleteNodeTransaction` and `InsertNodeTransaction` wastes
memory.  They should be done in an instance instead.

Fortunately, no edit action listener checks whether the deleted node is still
in the composed document or not, etc.  Therefore, we can simply notify them of
both deletion and insertion which were done in
`EditorBase::InsertNodeWithTransaction` and
`EditorBase::DeleteNodeWithTransaction`.  Note that previously, the range
updater needs to ignore the notifications from them while the node is being
moved.  However, it does not require anymore.  Therefore, this patch makes it
stop locking, and that would fix minor problem in the case of legacy mutation
event listeners run another edit action.

On the other hand, this changes some edge cases handling of
`MoveNodeWithTransaction` which are detected by the WPT.  According to the
previous result of applying this patch, `nsINode::InsertBefore` fails and that
leads some errors at updating the changed range.  I guess that the cause is
that there is some bugs at updating insertion point after deleting the node from
the DOM tree around here:
https://searchfox.org/mozilla-central/rev/0ffae75b690219858e5a45a39f8759a8aee7b9a2/editor/libeditor/HTMLEditor.cpp#5058-5071

However, it's safely fixed by the new code which does not remove the node from
the DOM tree explicitly.  So, I think that it's safe to accept this behavior
change for web apps in the wild.

Differential Revision: https://phabricator.services.mozilla.com/D146397
This commit is contained in:
Masayuki Nakano 2022-05-20 08:28:08 +00:00
Родитель cd689b44aa
Коммит c5b2a589bd
13 изменённых файлов: 477 добавлений и 102 удалений

Просмотреть файл

@ -385,6 +385,10 @@ enum class EditSubAction : int32_t {
// eDeleteNode indicates to remove a node from the DOM tree.
eDeleteNode,
// eMoveNode indicates to move a node connected in the DOM tree to different
// place.
eMoveNode,
// eSplitNode indicates to split a node to 2 nodes.
eSplitNode,

Просмотреть файл

@ -17,6 +17,7 @@
#include "InsertNodeTransaction.h"
#include "InsertTextTransaction.h"
#include "JoinNodesTransaction.h"
#include "MoveNodeTransaction.h"
#include "PlaceholderTransaction.h"
#include "ReplaceTextTransaction.h"
#include "SplitNodeTransaction.h"
@ -82,6 +83,7 @@ NS_IMPL_EDITTRANSACTIONBASE_GETASMETHODS(EditAggregateTransaction)
NS_IMPL_EDITTRANSACTIONBASE_GETASMETHODS(InsertNodeTransaction)
NS_IMPL_EDITTRANSACTIONBASE_GETASMETHODS(InsertTextTransaction)
NS_IMPL_EDITTRANSACTIONBASE_GETASMETHODS(JoinNodesTransaction)
NS_IMPL_EDITTRANSACTIONBASE_GETASMETHODS(MoveNodeTransaction)
NS_IMPL_EDITTRANSACTIONBASE_GETASMETHODS(PlaceholderTransaction)
NS_IMPL_EDITTRANSACTIONBASE_GETASMETHODS(ReplaceTextTransaction)
NS_IMPL_EDITTRANSACTIONBASE_GETASMETHODS(SplitNodeTransaction)

Просмотреть файл

@ -58,6 +58,7 @@ class EditTransactionBase : public nsITransaction {
NS_DECL_GETASTRANSACTION_BASE(InsertNodeTransaction)
NS_DECL_GETASTRANSACTION_BASE(InsertTextTransaction)
NS_DECL_GETASTRANSACTION_BASE(JoinNodesTransaction)
NS_DECL_GETASTRANSACTION_BASE(MoveNodeTransaction)
NS_DECL_GETASTRANSACTION_BASE(PlaceholderTransaction)
NS_DECL_GETASTRANSACTION_BASE(ReplaceTextTransaction)
NS_DECL_GETASTRANSACTION_BASE(SplitNodeTransaction)

Просмотреть файл

@ -1193,6 +1193,7 @@ class EditorBase : public nsIEditor,
TopLevelEditSubActionDataRef().Clear();
switch (mTopLevelEditSubAction) {
case EditSubAction::eInsertNode:
case EditSubAction::eMoveNode:
case EditSubAction::eCreateNode:
case EditSubAction::eSplitNode:
case EditSubAction::eInsertText:
@ -2874,6 +2875,7 @@ class EditorBase : public nsIEditor,
// ToGenericNSResult
friend class ListItemElementSelectionState; // AutoEditActionDataSetter,
// ToGenericNSResult
friend class MoveNodeTransaction; // ToGenericNSResult
friend class ParagraphStateAtSelection; // AutoEditActionDataSetter,
// ToGenericNSResult
friend class ReplaceTextTransaction; // AllowsTransactionsToChangeSelection,

Просмотреть файл

@ -100,6 +100,7 @@ class InterCiter; // InterCiter.h
class JoinNodesResult; // HTMLEditHelpers.h
class JoinNodesTransaction; // JoinNodesTransaction.h
class MoveNodeResult; // HTMLEditHelpers.h
class MoveNodeTransaction; // MoveNodeTransaction.h
class PlaceholderTransaction; // PlaceholderTransaction.h
class ReplaceTextTransaction; // ReplaceTextTransaction.h
class SplitNodeResult; // HTMLEditHelpers.h

Просмотреть файл

@ -13,6 +13,7 @@
#include "HTMLEditUtils.h"
#include "InsertNodeTransaction.h"
#include "JoinNodesTransaction.h"
#include "MoveNodeTransaction.h"
#include "ReplaceTextTransaction.h"
#include "SplitNodeTransaction.h"
#include "TypeInState.h"
@ -5127,46 +5128,72 @@ nsresult HTMLEditor::MoveNodeWithTransaction(
return NS_OK;
}
// Notify our internal selection state listener
AutoMoveNodeSelNotify selNotify(RangeUpdaterRef(), oldPoint, aPointToInsert);
nsresult rv = DeleteNodeWithTransaction(aContent);
if (NS_FAILED(rv)) {
NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
return rv;
}
// Mutation event listener could break insertion point. Let's check it.
auto pointToInsert = selNotify.ComputeInsertionPoint<EditorDOMPoint>();
if (NS_WARN_IF(!pointToInsert.IsSet())) {
RefPtr<MoveNodeTransaction> moveNodeTransaction =
MoveNodeTransaction::MaybeCreate(*this, aContent, aPointToInsert);
if (MOZ_UNLIKELY(!moveNodeTransaction)) {
NS_WARNING("MoveNodeTransaction::MaybeCreate() failed");
return NS_ERROR_FAILURE;
}
// If some children have removed from the container, let's append to the
// container.
// XXX Perhaps, if mutation event listener inserts or removes some children
// but the child node referring with aPointToInsert is still available,
// we should insert aContent before it. However, we should keep
// traditional behavior for now.
if (NS_WARN_IF(!pointToInsert.IsSetAndValid())) {
pointToInsert.SetToEndOf(pointToInsert.GetContainer());
}
CreateContentResult insertContentNodeResult =
InsertNodeWithTransaction(aContent, pointToInsert);
if (insertContentNodeResult.isErr()) {
NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
return insertContentNodeResult.unwrapErr();
}
rv = insertContentNodeResult.SuggestCaretPointTo(
*this, {SuggestCaret::OnlyIfHasSuggestion,
SuggestCaret::OnlyIfTransactionsAllowedToDoIt,
SuggestCaret::AndIgnoreTrivialError});
if (NS_FAILED(rv)) {
NS_WARNING("CreateContentResult::SuggestCaretPointTo() failed");
return rv;
IgnoredErrorResult ignoredError;
AutoEditSubActionNotifier startToHandleEditSubAction(
*this, EditSubAction::eMoveNode, nsIEditor::eNext, ignoredError);
if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
return ignoredError.StealNSResult();
}
NS_WARNING_ASSERTION(
rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
"CreateContentResult::SuggestCaretPointTo() failed, but ignored");
!ignoredError.Failed(),
"TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
TopLevelEditSubActionDataRef().WillDeleteContent(*this, aContent);
nsresult rv = DoTransactionInternal(moveNodeTransaction);
if (NS_SUCCEEDED(rv)) {
if (AllowsTransactionsToChangeSelection()) {
const auto pointToPutCaret =
moveNodeTransaction->SuggestPointToPutCaret<EditorRawDOMPoint>();
if (pointToPutCaret.IsSet()) {
nsresult rv = CollapseSelectionTo(pointToPutCaret);
if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
NS_WARNING(
"EditorBase::CollapseSelectionTo() caused destroying the editor");
return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"EditorBase::CollapseSelectionTo() failed, but ignored");
}
}
if (mTextServicesDocument) {
const OwningNonNull<TextServicesDocument> textServicesDocument =
*mTextServicesDocument;
textServicesDocument->DidDeleteContent(aContent);
}
}
if (!mActionListeners.IsEmpty()) {
for (auto& listener : mActionListeners.Clone()) {
DebugOnly<nsresult> rvIgnored = listener->DidDeleteNode(&aContent, rv);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored),
"nsIEditActionListener::DidDeleteNode() failed, but ignored");
}
}
if (MOZ_UNLIKELY(Destroyed())) {
NS_WARNING(
"MoveNodeTransaction::DoTransaction() caused destroying the editor");
return NS_ERROR_EDITOR_DESTROYED;
}
if (NS_FAILED(rv)) {
NS_WARNING("MoveNodeTransaction::DoTransaction() failed");
return rv;
}
TopLevelEditSubActionDataRef().DidInsertContent(*this, aContent);
return NS_OK;
}

Просмотреть файл

@ -4637,6 +4637,9 @@ class HTMLEditor final : public EditorBase,
// RefreshEditingUI,
// RemoveEmptyInclusiveAncestorInlineElements,
// mComposerUpdater, mHasBeforeINputBeenCancelded
friend class MoveNodeTransaction; // AllowsTransactionsToChangeSelection,
// CollapseSelectionTo, MarkElementDirty,
// RangeUpdaterRef
friend class JoinNodesTransaction; // DidJoinNodesTransaction, DoJoinNodes,
// DoSplitNode, RangeUpdaterRef
friend class

Просмотреть файл

@ -0,0 +1,295 @@
/* -*- 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)) ||
NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(
*aPointToInsert.GetContainer()))) {
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 notifyStoredRRanges(
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 notifyStoredRRanges(
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

Просмотреть файл

@ -0,0 +1,98 @@
/* -*- 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/. */
#ifndef MoveNodeTransaction_h
#define MoveNodeTransaction_h
#include "EditTransactionBase.h" // for EditTransactionBase, etc.
#include "EditorForwards.h"
#include "nsCOMPtr.h" // for nsCOMPtr
#include "nsCycleCollectionParticipant.h"
#include "nsIContent.h" // for nsIContent
#include "nsISupportsImpl.h" // for NS_DECL_ISUPPORTS_INHERITED
namespace mozilla {
/**
* A transaction that moves a content node to a specified point.
*/
class MoveNodeTransaction final : public EditTransactionBase {
protected:
template <typename PT, typename CT>
MoveNodeTransaction(HTMLEditor& aHTMLEditor, nsIContent& aContentToMove,
const EditorDOMPointBase<PT, CT>& aPointToInsert);
public:
/**
* Create a transaction for moving aContentToMove before the child at
* aPointToInsert.
*
* @param aHTMLEditor The editor which manages the transaction.
* @param aContentToMove The node to be moved.
* @param aPointToInsert The insertion point of aContentToMove.
* @return A MoveNodeTransaction which was initialized
* with the arguments.
*/
template <typename PT, typename CT>
static already_AddRefed<MoveNodeTransaction> MaybeCreate(
HTMLEditor& aHTMLEditor, nsIContent& aContentToMove,
const EditorDOMPointBase<PT, CT>& aPointToInsert);
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MoveNodeTransaction,
EditTransactionBase)
NS_DECL_EDITTRANSACTIONBASE
NS_DECL_EDITTRANSACTIONBASE_GETASMETHODS_OVERRIDE(MoveNodeTransaction)
MOZ_CAN_RUN_SCRIPT NS_IMETHOD RedoTransaction() override;
/**
* SuggestPointToPutCaret() suggests a point after doing or redoing the
* transaction.
*/
template <typename EditorDOMPointType>
EditorDOMPointType SuggestPointToPutCaret() const {
if (MOZ_UNLIKELY(!mContainer || !mContentToMove)) {
return EditorDOMPointType();
}
return EditorDOMPointType::After(mContentToMove);
}
friend std::ostream& operator<<(std::ostream& aStream,
const MoveNodeTransaction& aTransaction);
protected:
virtual ~MoveNodeTransaction() = default;
MOZ_CAN_RUN_SCRIPT nsresult DoTransactionInternal();
// The content which will be or was moved from mOldContainer to mContainer.
nsCOMPtr<nsIContent> mContentToMove;
// mContainer is new container of mContentToInsert after (re-)doing the
// transaction.
nsCOMPtr<nsINode> mContainer;
// mReference is the child content where mContentToMove should be or was
// inserted into the mContainer. This is typically the next sibling of
// mContentToMove after moving.
nsCOMPtr<nsIContent> mReference;
// mOldContainer is the original container of mContentToMove before moving.
nsCOMPtr<nsINode> mOldContainer;
// mOldNextSibling is the next sibling of mCOntentToMove before moving.
nsCOMPtr<nsIContent> mOldNextSibling;
// The editor for this transaction.
RefPtr<HTMLEditor> mHTMLEditor;
};
} // namespace mozilla
#endif // #ifndef MoveNodeTransaction_h

Просмотреть файл

@ -505,11 +505,10 @@ void RangeUpdater::DidRemoveContainer(const Element& aRemovedElement,
void RangeUpdater::DidMoveNode(const nsINode& aOldParent, uint32_t aOldOffset,
const nsINode& aNewParent, uint32_t aNewOffset) {
if (NS_WARN_IF(!mLocked)) {
if (mLocked) {
// Do nothing if moving nodes is occurred while changing the container.
return;
}
mLocked = false;
for (RefPtr<RangeItem>& rangeItem : mArray) {
if (NS_WARN_IF(!rangeItem)) {
return;

Просмотреть файл

@ -272,7 +272,6 @@ class MOZ_STACK_CLASS RangeUpdater final {
NS_WARNING_ASSERTION(mLocked, "Not locked");
mLocked = false;
}
void WillMoveNode() { mLocked = true; }
void DidMoveNode(const nsINode& aOldParent, uint32_t aOldOffset,
const nsINode& aNewParent, uint32_t aNewOffset);
@ -505,15 +504,15 @@ class MOZ_STACK_CLASS AutoInsertContainerSelNotify final {
/**
* Another helper class for SelectionState. Stack based class for doing
* Will/DidMoveNode()
* DidMoveNode()
*/
class MOZ_STACK_CLASS AutoMoveNodeSelNotify final {
public:
AutoMoveNodeSelNotify() = delete;
AutoMoveNodeSelNotify(RangeUpdater& aRangeUpdater,
const EditorDOMPoint& aOldPoint,
const EditorDOMPoint& aNewPoint)
const EditorRawDOMPoint& aOldPoint,
const EditorRawDOMPoint& aNewPoint)
: mRangeUpdater(aRangeUpdater),
mOldParent(*aOldPoint.GetContainer()),
mNewParent(*aNewPoint.GetContainer()),
@ -521,27 +520,18 @@ class MOZ_STACK_CLASS AutoMoveNodeSelNotify final {
mNewOffset(aNewPoint.Offset()) {
MOZ_ASSERT(aOldPoint.IsSet());
MOZ_ASSERT(aNewPoint.IsSet());
mRangeUpdater.WillMoveNode();
}
~AutoMoveNodeSelNotify() {
mRangeUpdater.DidMoveNode(mOldParent, mOldOffset, mNewParent, mNewOffset);
}
template <typename EditorDOMPointType>
EditorDOMPointType ComputeInsertionPoint() const {
if (&mOldParent == &mNewParent && mOldOffset < mNewOffset) {
return EditorDOMPointType(&mNewParent, mNewOffset - 1);
}
return EditorDOMPointType(&mNewParent, mNewOffset);
}
private:
RangeUpdater& mRangeUpdater;
nsINode& mOldParent;
nsINode& mNewParent;
uint32_t mOldOffset;
uint32_t mNewOffset;
const uint32_t mOldOffset;
const uint32_t mNewOffset;
};
} // namespace mozilla

Просмотреть файл

@ -70,6 +70,7 @@ UNIFIED_SOURCES += [
"InsertTextTransaction.cpp",
"InternetCiter.cpp",
"JoinNodesTransaction.cpp",
"MoveNodeTransaction.cpp",
"PlaceholderTransaction.cpp",
"ReplaceTextTransaction.cpp",
"SelectionState.cpp",

Просмотреть файл

@ -56,9 +56,6 @@
[Backspace at "<ul><li><ol><li>{}<br></li></ol></li><li>list-item2</li></ul>"]
expected: FAIL
[Backspace at "<ul><li>[list-item1</li><ul><li>}list-item2</li></ul></ul>" - comparing innerHTML]
expected: FAIL
[Backspace at "<ul><li>[list-item1</li></ul><ol><li><ul><li>list-item2\]</li></ul></li></ol>"]
expected: FAIL
@ -290,18 +287,12 @@
[Backspace at "<ul><ul><li>[list-item1</li></ul><li>}list-item2</li></ul>"]
expected: FAIL
[Backspace at "<ul><li>[list-item1</li><ol><li>list-item2</li><li>}list-item3</li></ol></ul>" - comparing innerHTML]
expected: FAIL
[Backspace at "<ul><ol><li>[list-item1</li></ol><li>}list-item2</li></ul>"]
expected: FAIL
[Backspace at "<ul><li>list-item1[</li><li>list-item2\]</li></ul><ol><li>list-item3</li></ol>" - comparing innerHTML]
expected: FAIL
[Backspace at "<ul><li>[list-item1</li><ul><li>list-item2</li><li>}list-item3</li></ul></ul>" - comparing innerHTML]
expected: FAIL
[Backspace at "<ul><ol><li>[list-item1</li></ol><li>}list-item2</li></ul>" - comparing innerHTML]
expected: FAIL
@ -404,9 +395,6 @@
[Backspace at "<ul><li>list-item1[</li></ul><ul><ul><li>}list-item2</li></ul></ul>" - comparing innerHTML]
expected: FAIL
[Backspace at "<ul><li>[list-item1</li><ol><li>}list-item2</li></ul></ul>" - comparing innerHTML]
expected: FAIL
[Backspace at "<ul><ul><li>[list-item1</li></ul></ul><ol><li>}list-item2</li></ol>" - comparing innerHTML]
expected: FAIL
@ -847,9 +835,6 @@
[Delete at "<ul><li>[list-item1</li></ul><ul><li><ol><li>list-item2\]</li></ol></li></ul>" - comparing innerHTML]
expected: FAIL
[Delete at "<ul><li>[list-item1</li><ul><li>list-item2</li><li>}list-item3</li></ul></ul>" - comparing innerHTML]
expected: FAIL
[Delete at "<ul><li><ul><li>[list-item1</li></ul></li></ul><ol><li>list-item2\]</li></ol>" - comparing innerHTML]
expected: FAIL
@ -898,9 +883,6 @@
[Delete at "<ul><li><ol><li>[list-item1</li></ol></li></ul><ol><li>}list-item2</li></ol>"]
expected: FAIL
[Delete at "<ul><li>[list-item1</li><ol><li>list-item2</li><li>}list-item3</li></ol></ul>" - comparing innerHTML]
expected: FAIL
[Delete at "<ul><li><ol><li>[list-item1</li></ol></li></ul><ul><li>list-item2\]</li></ul>"]
expected: FAIL
@ -991,9 +973,6 @@
[Delete at "<ul><li>[list-item1</li></ul><ul><ul><li>}list-item2</li></ul></ul>"]
expected: FAIL
[Delete at "<ul><li>[list-item1</li><ul><li>}list-item2</li></ul></ul>" - comparing innerHTML]
expected: FAIL
[Delete at "<ul><li>[list-item1</li><ul><li>list-item2</li><li>}list-item3</li></ul></ul>"]
expected: FAIL
@ -1057,9 +1036,6 @@
[Delete at "<ul><li>list-item1[</li></ul><ol><ol><li>}list-item2<br>second line of list-item2</li></ol></ol>" - comparing innerHTML]
expected: FAIL
[Delete at "<ul><li>[list-item1</li><ol><li>}list-item2</li></ul></ul>" - comparing innerHTML]
expected: FAIL
[Delete at "<ul><li>[list-item1</li></ul><ol><li><ol><li>}list-item2</li></ol></li></ol>"]
expected: FAIL
@ -1206,9 +1182,6 @@
[Backspace at "<ol><li><ul><li>[list-item1</li></ul></li></ol><ul><li>}list-item2</li></ul>" - comparing innerHTML]
expected: FAIL
[Backspace at "<ol><li>[list-item1</li><ul><li>list-item2</li><li>}list-item3</li></ul></ol>" - comparing innerHTML]
expected: FAIL
[Backspace at "<ol><li>[list-item1</li></ol><ul><li><ul><li>}list-item2</li></ul></li></ul>"]
expected: FAIL
@ -1497,15 +1470,9 @@
[Backspace at "<ol><li>[list-item1</li></ol><ul><ul><li>list-item2\]</li></ul></ul>"]
expected: FAIL
[Backspace at "<ol><li>[list-item1</li><ul><li>}list-item2</li></ul></ol>" - comparing innerHTML]
expected: FAIL
[Backspace at "<ol><li>list-item1[</li></ol><ul><li><ul><li>}list-item2</li></ul></li></ul>" - comparing innerHTML]
expected: FAIL
[Backspace at "<ol><li>[list-item1</li><ol><li>list-item2</li><li>}list-item3</li></ol></ol>" - comparing innerHTML]
expected: FAIL
[Backspace at "<ol><li>list-item1[</li><li>list-item2\]</li></ol><ul><li>list-item3</li></ul>"]
expected: FAIL
@ -1647,9 +1614,6 @@
[Backspace at "<ol><li>list-item1[</li></ol><ul><ol><li>}list-item2<br>second line of list-item2</li></ol></ul>" - comparing innerHTML]
expected: FAIL
[Backspace at "<ol><li>[list-item1</li><ol><li>}list-item2</li></ul></ol>" - comparing innerHTML]
expected: FAIL
[Backspace at "<ol><li>[list-item1</li></ol><ul><li>}list-item2</li></ul>"]
expected: FAIL
@ -1853,9 +1817,6 @@
[Delete at "<ol><ul><li>{}<br></li></ul></ol>" - comparing innerHTML]
expected: FAIL
[Delete at "<ol><li>[list-item1</li><ul><li>list-item2</li><li>}list-item3</li></ul></ol>" - comparing innerHTML]
expected: FAIL
[Delete at "<ol><li>list-item1[</li></ol><ol><li>}list-item2</li></ol>" - comparing innerHTML]
expected: FAIL
@ -2102,9 +2063,6 @@
[Delete at "<ol><li>list-item1</li><ol><li>{}<br></li></ol></ol>"]
expected: FAIL
[Delete at "<ol><li>[list-item1</li><ul><li>}list-item2</li></ul></ol>" - comparing innerHTML]
expected: FAIL
[Delete at "<ol><li>list-item1[</li></ol><ul><li>}list-item2<br>second line in list-item2</li></ul>" - comparing innerHTML]
expected: FAIL
@ -2201,9 +2159,6 @@
[Delete at "<ol><li><ol><li>{}<br></li></ol></li><li>list-item2</li></ol>"]
expected: FAIL
[Delete at "<ol><li>[list-item1</li><ol><li>}list-item2</li></ul></ol>" - comparing innerHTML]
expected: FAIL
[Delete at "<ol><ol><li>{}<br></li></ol></ol>" - comparing innerHTML]
expected: FAIL
@ -2264,9 +2219,6 @@
[Delete at "<ol><li>[list-item1</li></ol><ul><li>list-item2\]</li></ul>" - comparing innerHTML]
expected: FAIL
[Delete at "<ol><li>[list-item1</li><ol><li>list-item2</li><li>}list-item3</li></ol></ol>" - comparing innerHTML]
expected: FAIL
[Delete at "<ol><li><ol><li>[list-item1</li></ol></li></ol><ol><li>list-item2\]</li></ol>"]
expected: FAIL