1
0
Fork 0
firefox/editor/libeditor/ReplaceTextTransaction.cpp
Daniel Baumann 5e9a113729
Adding upstream version 140.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-25 09:37:52 +02:00

254 lines
9.6 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 "ReplaceTextTransaction.h"
#include "HTMLEditUtils.h"
#include "mozilla/Logging.h"
#include "mozilla/OwningNonNull.h"
#include "mozilla/TextEditor.h"
#include "mozilla/ToString.h"
namespace mozilla {
using namespace dom;
// static
already_AddRefed<ReplaceTextTransaction> ReplaceTextTransaction::Create(
EditorBase& aEditorBase, const nsAString& aStringToInsert,
dom::Text& aTextNode, uint32_t aStartOffset, uint32_t aLength) {
MOZ_ASSERT(aLength > 0, "Use InsertTextTransaction instead");
MOZ_ASSERT(!aStringToInsert.IsEmpty(), "Use DeleteTextTransaction instead");
MOZ_ASSERT(aTextNode.Length() >= aStartOffset);
MOZ_ASSERT(aTextNode.Length() >= aStartOffset + aLength);
RefPtr<ReplaceTextTransaction> transaction =
aEditorBase.IsTextEditor()
? new ReplaceTextTransaction(aEditorBase, aStringToInsert, aTextNode,
aStartOffset, aLength)
: new ReplaceTextInTextNodeTransaction(
aEditorBase, aStringToInsert, aTextNode, aStartOffset, aLength);
return transaction.forget();
}
std::ostream& operator<<(std::ostream& aStream,
const ReplaceTextTransaction& aTransaction) {
const auto* transactionForHTMLEditor =
aTransaction.GetAsReplaceTextInTextNodeTransaction();
if (transactionForHTMLEditor) {
return aStream << *transactionForHTMLEditor;
}
aStream << "{ mStringToInsert=\""
<< NS_ConvertUTF16toUTF8(aTransaction.mStringToInsert).get() << "\""
<< ", mStringToBeReplaced=\""
<< NS_ConvertUTF16toUTF8(aTransaction.mStringToBeReplaced).get()
<< "\", mOffset=" << aTransaction.mOffset
<< ", mEditorBase=" << aTransaction.mEditorBase.get() << " }";
return aStream;
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(ReplaceTextTransaction, EditTransactionBase,
mEditorBase)
NS_IMPL_ADDREF_INHERITED(ReplaceTextTransaction, EditTransactionBase)
NS_IMPL_RELEASE_INHERITED(ReplaceTextTransaction, EditTransactionBase)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReplaceTextTransaction)
NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
Text* ReplaceTextTransaction::GetTextNode() const {
if (MOZ_UNLIKELY(!mEditorBase)) {
return nullptr;
}
if (TextEditor* const textEditor = mEditorBase->GetAsTextEditor()) {
return textEditor->GetTextNode();
}
MOZ_ASSERT(GetAsReplaceTextInTextNodeTransaction());
return GetAsReplaceTextInTextNodeTransaction()->mTextNode;
}
NS_IMETHODIMP ReplaceTextTransaction::DoTransaction() {
MOZ_LOG(GetLogModule(), LogLevel::Info,
("%p ReplaceTextTransaction::%s this=%s", this, __FUNCTION__,
ToString(*this).c_str()));
if (NS_WARN_IF(!mEditorBase)) {
return NS_ERROR_NOT_AVAILABLE;
}
const RefPtr<Text> textNode = GetTextNode();
if (NS_WARN_IF(!textNode) ||
(mEditorBase->IsHTMLEditor() &&
NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(*textNode)))) {
return NS_ERROR_NOT_AVAILABLE;
}
const OwningNonNull<EditorBase> editorBase = *mEditorBase;
IgnoredErrorResult error;
editorBase->DoReplaceText(*textNode, mOffset, mStringToBeReplaced.Length(),
mStringToInsert, error);
if (MOZ_UNLIKELY(error.Failed())) {
NS_WARNING("EditorBase::DoReplaceText() failed");
return error.StealNSResult();
}
// XXX What should we do if mutation event listener changed the node?
editorBase->RangeUpdaterRef().SelAdjReplaceText(*textNode, mOffset,
mStringToBeReplaced.Length(),
mStringToInsert.Length());
return NS_OK;
}
NS_IMETHODIMP ReplaceTextTransaction::UndoTransaction() {
MOZ_LOG(GetLogModule(), LogLevel::Info,
("%p ReplaceTextTransaction::%s this=%s", this, __FUNCTION__,
ToString(*this).c_str()));
if (NS_WARN_IF(!mEditorBase)) {
return NS_ERROR_NOT_AVAILABLE;
}
const RefPtr<Text> textNode = GetTextNode();
if (NS_WARN_IF(!textNode) ||
(mEditorBase->IsHTMLEditor() &&
NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(*textNode)))) {
return NS_ERROR_NOT_AVAILABLE;
}
IgnoredErrorResult error;
nsAutoString insertedString;
textNode->SubstringData(mOffset, mStringToInsert.Length(), insertedString,
error);
if (MOZ_UNLIKELY(error.Failed())) {
NS_WARNING("CharacterData::SubstringData() failed");
return error.StealNSResult();
}
if (MOZ_UNLIKELY(insertedString != mStringToInsert)) {
NS_WARNING(
"ReplaceTextTransaction::UndoTransaction() did nothing due to "
"unexpected text");
return NS_OK;
}
const OwningNonNull<EditorBase> editorBase = *mEditorBase;
editorBase->DoReplaceText(*textNode, mOffset, mStringToInsert.Length(),
mStringToBeReplaced, error);
if (MOZ_UNLIKELY(error.Failed())) {
NS_WARNING("EditorBase::DoReplaceText() failed");
return error.StealNSResult();
}
// XXX What should we do if mutation event listener changed the node?
editorBase->RangeUpdaterRef().SelAdjReplaceText(*textNode, mOffset,
mStringToInsert.Length(),
mStringToBeReplaced.Length());
if (!editorBase->AllowsTransactionsToChangeSelection()) {
return NS_OK;
}
// XXX Should we stop setting selection when mutation event listener
// modifies the text node?
editorBase->CollapseSelectionTo(
EditorRawDOMPoint(textNode, mOffset + mStringToBeReplaced.Length()),
error);
if (MOZ_UNLIKELY(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
NS_WARNING(
"EditorBase::CollapseSelectionTo() caused destroying the editor");
return NS_ERROR_EDITOR_DESTROYED;
}
NS_ASSERTION(!error.Failed(),
"EditorBase::CollapseSelectionTo() failed, but ignored");
return NS_OK;
}
NS_IMETHODIMP ReplaceTextTransaction::RedoTransaction() {
MOZ_LOG(GetLogModule(), LogLevel::Info,
("%p ReplaceTextTransaction::%s this=%s", this, __FUNCTION__,
ToString(*this).c_str()));
if (NS_WARN_IF(!mEditorBase)) {
return NS_ERROR_NOT_AVAILABLE;
}
const RefPtr<Text> textNode = GetTextNode();
if (NS_WARN_IF(!textNode) ||
(mEditorBase->IsHTMLEditor() &&
NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(*textNode)))) {
return NS_ERROR_NOT_AVAILABLE;
}
IgnoredErrorResult error;
nsAutoString undoneString;
textNode->SubstringData(mOffset, mStringToBeReplaced.Length(), undoneString,
error);
if (MOZ_UNLIKELY(error.Failed())) {
NS_WARNING("CharacterData::SubstringData() failed");
return error.StealNSResult();
}
if (MOZ_UNLIKELY(undoneString != mStringToBeReplaced)) {
NS_WARNING(
"ReplaceTextTransaction::RedoTransaction() did nothing due to "
"unexpected text");
return NS_OK;
}
const OwningNonNull<EditorBase> editorBase = *mEditorBase;
editorBase->DoReplaceText(*textNode, mOffset, mStringToBeReplaced.Length(),
mStringToInsert, error);
if (MOZ_UNLIKELY(error.Failed())) {
NS_WARNING("EditorBase::DoReplaceText() failed");
return error.StealNSResult();
}
// XXX What should we do if mutation event listener changed the node?
editorBase->RangeUpdaterRef().SelAdjReplaceText(*textNode, mOffset,
mStringToBeReplaced.Length(),
mStringToInsert.Length());
if (!editorBase->AllowsTransactionsToChangeSelection()) {
return NS_OK;
}
// XXX Should we stop setting selection when mutation event listener
// modifies the text node?
editorBase->CollapseSelectionTo(SuggestPointToPutCaret<EditorRawDOMPoint>(),
error);
if (MOZ_UNLIKELY(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
NS_WARNING(
"EditorBase::CollapseSelectionTo() caused destroying the editor");
return NS_ERROR_EDITOR_DESTROYED;
}
NS_ASSERTION(!error.Failed(),
"EditorBase::CollapseSelectionTo() failed, but ignored");
return NS_OK;
}
/******************************************************************************
*
******************************************************************************/
std::ostream& operator<<(std::ostream& aStream,
const ReplaceTextInTextNodeTransaction& aTransaction) {
aStream << "{ mTextNode=" << aTransaction.mTextNode.get();
if (aTransaction.mTextNode) {
aStream << " (" << *aTransaction.mTextNode << ")";
}
aStream << ", mStringToInsert=\""
<< NS_ConvertUTF16toUTF8(aTransaction.mStringToInsert).get() << "\""
<< ", mStringToBeReplaced=\""
<< NS_ConvertUTF16toUTF8(aTransaction.mStringToBeReplaced).get()
<< "\", mOffset=" << aTransaction.mOffset
<< ", mEditorBase=" << aTransaction.mEditorBase.get() << " }";
return aStream;
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(ReplaceTextInTextNodeTransaction,
ReplaceTextTransaction, mTextNode)
NS_IMPL_ADDREF_INHERITED(ReplaceTextInTextNodeTransaction,
ReplaceTextTransaction)
NS_IMPL_RELEASE_INHERITED(ReplaceTextInTextNodeTransaction,
ReplaceTextTransaction)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReplaceTextInTextNodeTransaction)
NS_INTERFACE_MAP_END_INHERITING(ReplaceTextTransaction)
} // namespace mozilla