зеркало из https://github.com/mozilla/gecko-dev.git
301 строка
12 KiB
C++
301 строка
12 KiB
C++
/* -*- 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
|