зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
cd689b44aa
Коммит
c5b2a589bd
|
@ -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
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче