/* -*- 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 "InsertTextTransaction.h" #include "ErrorList.h" #include "mozilla/EditorBase.h" // mEditorBase #include "mozilla/Logging.h" #include "mozilla/SelectionState.h" // RangeUpdater #include "mozilla/TextEditor.h" // TextEditor #include "mozilla/ToString.h" #include "mozilla/dom/Selection.h" // Selection local var #include "mozilla/dom/Text.h" // mTextNode #include "nsAString.h" // nsAString parameter #include "nsDebug.h" // for NS_ASSERTION, etc. #include "nsError.h" // for NS_OK, etc. #include "nsQueryObject.h" // for do_QueryObject namespace mozilla { using namespace dom; // static already_AddRefed InsertTextTransaction::Create( EditorBase& aEditorBase, const nsAString& aStringToInsert, const EditorDOMPointInText& aPointToInsert) { MOZ_ASSERT(aPointToInsert.IsSetAndValid()); RefPtr transaction = aEditorBase.IsTextEditor() ? new InsertTextTransaction(aEditorBase, aStringToInsert, aPointToInsert) : new InsertTextIntoTextNodeTransaction(aEditorBase, aStringToInsert, aPointToInsert); return transaction.forget(); } InsertTextTransaction::InsertTextTransaction( EditorBase& aEditorBase, const nsAString& aStringToInsert, const EditorDOMPointInText& aPointToInsert) : mEditorBase(&aEditorBase), mStringToInsert(aStringToInsert), mOffset(aPointToInsert.Offset()) {} std::ostream& operator<<(std::ostream& aStream, const InsertTextTransaction& aTransaction) { const auto* transactionForHTMLEditor = aTransaction.GetAsInsertTextIntoTextNodeTransaction(); if (transactionForHTMLEditor) { return aStream << *transactionForHTMLEditor; } aStream << "{ mOffset=" << aTransaction.mOffset << ", mStringToInsert=\"" << NS_ConvertUTF16toUTF8(aTransaction.mStringToInsert).get() << "\"" << ", mEditorBase=" << aTransaction.mEditorBase.get() << " }"; return aStream; } NS_IMPL_CYCLE_COLLECTION_INHERITED(InsertTextTransaction, EditTransactionBase, mEditorBase) NS_IMPL_ADDREF_INHERITED(InsertTextTransaction, EditTransactionBase) NS_IMPL_RELEASE_INHERITED(InsertTextTransaction, EditTransactionBase) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(InsertTextTransaction) NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase) Text* InsertTextTransaction::GetTextNode() const { if (MOZ_UNLIKELY(!mEditorBase)) { return nullptr; } if (TextEditor* const textEditor = mEditorBase->GetAsTextEditor()) { return textEditor->GetTextNode(); } MOZ_ASSERT(GetAsInsertTextIntoTextNodeTransaction()); return GetAsInsertTextIntoTextNodeTransaction()->mTextNode; } NS_IMETHODIMP InsertTextTransaction::DoTransaction() { MOZ_LOG(GetLogModule(), LogLevel::Info, ("%p InsertTextTransaction::%s this=%s", this, __FUNCTION__, ToString(*this).c_str())); if (NS_WARN_IF(!mEditorBase)) { return NS_ERROR_NOT_AVAILABLE; } const RefPtr textNode = GetTextNode(); if (NS_WARN_IF(!textNode)) { return NS_ERROR_NOT_AVAILABLE; } const OwningNonNull editorBase = *mEditorBase; ErrorResult error; editorBase->DoInsertText(*textNode, mOffset, mStringToInsert, error); if (error.Failed()) { NS_WARNING("EditorBase::DoInsertText() failed"); return error.StealNSResult(); } editorBase->RangeUpdaterRef().SelAdjInsertText(*textNode, mOffset, mStringToInsert.Length()); return NS_OK; } NS_IMETHODIMP InsertTextTransaction::UndoTransaction() { MOZ_LOG(GetLogModule(), LogLevel::Info, ("%p InsertTextTransaction::%s this=%s", this, __FUNCTION__, ToString(*this).c_str())); if (NS_WARN_IF(!mEditorBase)) { return NS_ERROR_NOT_INITIALIZED; } const RefPtr textNode = GetTextNode(); if (NS_WARN_IF(!textNode)) { return NS_ERROR_NOT_AVAILABLE; } const OwningNonNull editorBase = *mEditorBase; ErrorResult error; editorBase->DoDeleteText(*textNode, mOffset, mStringToInsert.Length(), error); NS_WARNING_ASSERTION(!error.Failed(), "EditorBase::DoDeleteText() failed"); return error.StealNSResult(); } NS_IMETHODIMP InsertTextTransaction::RedoTransaction() { MOZ_LOG(GetLogModule(), LogLevel::Info, ("%p InsertTextTransaction::%s this=%s", this, __FUNCTION__, !mEditorBase || mEditorBase->IsTextEditor() ? ToString(*this).c_str() : ToString(*GetAsInsertTextIntoTextNodeTransaction()).c_str())); nsresult rv = DoTransaction(); if (NS_FAILED(rv)) { NS_WARNING("InsertTextTransaction::DoTransaction() failed"); return rv; } if (RefPtr editorBase = mEditorBase) { nsresult rv = editorBase->CollapseSelectionTo( SuggestPointToPutCaret()); if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { return NS_ERROR_EDITOR_DESTROYED; } NS_WARNING_ASSERTION( NS_SUCCEEDED(rv), "EditorBase::CollapseSelectionTo() failed, but ignored"); } return NS_OK; } NS_IMETHODIMP InsertTextTransaction::Merge(nsITransaction* aOtherTransaction, bool* aDidMerge) { MOZ_LOG(GetLogModule(), LogLevel::Debug, ("%p InsertTextTransaction::%s(aOtherTransaction=%p) this=%s", this, __FUNCTION__, aOtherTransaction, ToString(*this).c_str())); if (NS_WARN_IF(!aOtherTransaction) || NS_WARN_IF(!aDidMerge)) { return NS_ERROR_INVALID_ARG; } // Set out param default value *aDidMerge = false; RefPtr otherTransactionBase = aOtherTransaction->GetAsEditTransactionBase(); if (!otherTransactionBase) { MOZ_LOG( GetLogModule(), LogLevel::Debug, ("%p InsertTextTransaction::%s(aOtherTransaction=%p) returned false", this, __FUNCTION__, aOtherTransaction)); return NS_OK; } // If aTransaction is a InsertTextTransaction, and if the selection hasn't // changed, then absorb it. InsertTextTransaction* otherInsertTextTransaction = otherTransactionBase->GetAsInsertTextTransaction(); if (!otherInsertTextTransaction || !IsSequentialInsert(*otherInsertTextTransaction)) { MOZ_LOG( GetLogModule(), LogLevel::Debug, ("%p InsertTextTransaction::%s(aOtherTransaction=%p) returned false", this, __FUNCTION__, aOtherTransaction)); return NS_OK; } mStringToInsert += otherInsertTextTransaction->GetData(); *aDidMerge = true; MOZ_LOG(GetLogModule(), LogLevel::Debug, ("%p InsertTextTransaction::%s(aOtherTransaction=%p) returned true", this, __FUNCTION__, aOtherTransaction)); return NS_OK; } bool InsertTextTransaction::IsSequentialInsert( InsertTextTransaction& aOtherTransaction) const { return aOtherTransaction.GetTextNode() == GetTextNode() && aOtherTransaction.mOffset == mOffset + mStringToInsert.Length(); } /****************************************************************************** * mozilla::InsertTextIntoTextNodeTransaction ******************************************************************************/ InsertTextIntoTextNodeTransaction::InsertTextIntoTextNodeTransaction( EditorBase& aEditorBase, const nsAString& aStringToInsert, const EditorDOMPointInText& aPointToInsert) : InsertTextTransaction(aEditorBase, aStringToInsert, aPointToInsert), mTextNode(aPointToInsert.ContainerAs()) { MOZ_ASSERT(aEditorBase.IsHTMLEditor()); } std::ostream& operator<<( std::ostream& aStream, const InsertTextIntoTextNodeTransaction& aTransaction) { aStream << "{ mTextNode=" << aTransaction.mTextNode.get(); if (aTransaction.mTextNode) { aStream << " (" << *aTransaction.mTextNode << ")"; } aStream << ", mOffset=" << aTransaction.mOffset << ", mStringToInsert=\"" << NS_ConvertUTF16toUTF8(aTransaction.mStringToInsert).get() << "\"" << ", mEditorBase=" << aTransaction.mEditorBase.get() << " }"; return aStream; } NS_IMPL_CYCLE_COLLECTION_INHERITED(InsertTextIntoTextNodeTransaction, InsertTextTransaction, mTextNode) NS_IMPL_ADDREF_INHERITED(InsertTextIntoTextNodeTransaction, InsertTextTransaction) NS_IMPL_RELEASE_INHERITED(InsertTextIntoTextNodeTransaction, InsertTextTransaction) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(InsertTextIntoTextNodeTransaction) NS_INTERFACE_MAP_END_INHERITING(InsertTextTransaction) } // namespace mozilla