Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
#include "TransactionItem.h"
#include "mozilla/mozalloc.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/IntegerRange.h"
#include "mozilla/OwningNonNull.h"
#include "mozilla/TransactionManager.h"
#include "mozilla/TransactionStack.h"
#include "nsCOMPtr.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsISupportsImpl.h"
#include "nsITransaction.h"
namespace mozilla {
TransactionItem::TransactionItem(nsITransaction* aTransaction)
: mTransaction(aTransaction), mUndoStack(0), mRedoStack(0) {}
TransactionItem::~TransactionItem() {
delete mRedoStack;
delete mUndoStack;
}
void TransactionItem::CleanUp() {
mData.Clear();
mTransaction = nullptr;
if (mRedoStack) {
mRedoStack->DoUnlink();
}
if (mUndoStack) {
mUndoStack->DoUnlink();
}
}
NS_IMPL_CYCLE_COLLECTING_NATIVE_ADDREF(TransactionItem)
NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE_WITH_LAST_RELEASE(TransactionItem,
CleanUp())
NS_IMPL_CYCLE_COLLECTION_CLASS(TransactionItem)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(TransactionItem)
tmp->CleanUp();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(TransactionItem)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mData)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTransaction)
if (tmp->mRedoStack) {
tmp->mRedoStack->DoTraverse(cb);
}
if (tmp->mUndoStack) {
tmp->mUndoStack->DoTraverse(cb);
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
nsresult TransactionItem::AddChild(TransactionItem& aTransactionItem) {
if (!mUndoStack) {
mUndoStack = new TransactionStack(TransactionStack::FOR_UNDO);
}
mUndoStack->Push(&aTransactionItem);
return NS_OK;
}
already_AddRefed<nsITransaction> TransactionItem::GetTransaction() {
return do_AddRef(mTransaction);
}
nsresult TransactionItem::DoTransaction() {
if (!mTransaction) {
return NS_OK;
}
OwningNonNull<nsITransaction> transaction = *mTransaction;
nsresult rv = transaction->DoTransaction();
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"nsITransaction::DoTransaction() failed");
return rv;
}
nsresult TransactionItem::UndoTransaction(
TransactionManager* aTransactionManager) {
nsresult rv = UndoChildren(aTransactionManager);
if (NS_FAILED(rv)) {
NS_WARNING("TransactionItem::UndoChildren() failed");
DebugOnly<nsresult> rvIgnored = RecoverFromUndoError(aTransactionManager);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"TransactionItem::RecoverFromUndoError() failed");
return rv;
}
if (!mTransaction) {
return NS_OK;
}
OwningNonNull<nsITransaction> transaction = *mTransaction;
rv = transaction->UndoTransaction();
if (NS_SUCCEEDED(rv)) {
return NS_OK;
}
NS_WARNING("TransactionItem::UndoTransaction() failed");
DebugOnly<nsresult> rvIgnored = RecoverFromUndoError(aTransactionManager);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"TransactionItem::RecoverFromUndoError() failed");
return rv;
}
nsresult TransactionItem::UndoChildren(
TransactionManager* aTransactionManager) {
if (!mUndoStack) {
return NS_OK;
}
if (!mRedoStack) {
mRedoStack = new TransactionStack(TransactionStack::FOR_REDO);
}
const size_t undoStackSize = mUndoStack->GetSize();
nsresult rv = NS_OK;
for ([[maybe_unused]] const size_t undoneCount :
IntegerRange(undoStackSize)) {
RefPtr<TransactionItem> transactionItem = mUndoStack->Peek();
if (!transactionItem) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsITransaction> transaction = transactionItem->GetTransaction();
rv = transactionItem->UndoTransaction(aTransactionManager);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"TransactionItem::UndoTransaction() failed");
if (NS_SUCCEEDED(rv)) {
transactionItem = mUndoStack->Pop();
mRedoStack->Push(transactionItem.forget());
}
if (transaction) {
aTransactionManager->DidUndoNotify(*transaction, rv);
}
}
// NS_OK if there is no Undo items or all methods work fine, otherwise,
// the result of the last item's UndoTransaction().
return rv;
}
nsresult TransactionItem::RedoTransaction(
TransactionManager* aTransactionManager) {
nsCOMPtr<nsITransaction> transaction(mTransaction);
if (transaction) {
nsresult rv = transaction->RedoTransaction();
if (NS_FAILED(rv)) {
NS_WARNING("nsITransaction::RedoTransaction() failed");
return rv;
}
}
nsresult rv = RedoChildren(aTransactionManager);
if (NS_SUCCEEDED(rv)) {
return NS_OK;
}
NS_WARNING("TransactionItem::RedoChildren() failed");
DebugOnly<nsresult> rvIgnored = RecoverFromRedoError(aTransactionManager);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"TransactionItem::RecoverFromRedoError() failed");
return rv;
}
nsresult TransactionItem::RedoChildren(
TransactionManager* aTransactionManager) {
if (!mRedoStack) {
return NS_OK;
}
/* Redo all of the transaction items children! */
const size_t redoStackSize = mRedoStack->GetSize();
nsresult rv = NS_OK;
for ([[maybe_unused]] const size_t redoneCount :
IntegerRange(redoStackSize)) {
RefPtr<TransactionItem> transactionItem = mRedoStack->Peek();
if (NS_WARN_IF(!transactionItem)) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsITransaction> transaction = transactionItem->GetTransaction();
rv = transactionItem->RedoTransaction(aTransactionManager);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"TransactionItem::RedoTransaction() failed");
if (NS_SUCCEEDED(rv)) {
transactionItem = mRedoStack->Pop();
mUndoStack->Push(transactionItem.forget());
}
if (transaction) {
aTransactionManager->DidUndoNotify(*transaction, rv);
}
}
// NS_OK if there is no Redo items or all methods work fine, otherwise,
// the result of the last item's RedoTransaction().
return rv;
}
size_t TransactionItem::NumberOfUndoItems() const {
NS_WARNING_ASSERTION(!mUndoStack || mUndoStack->GetSize() > 0,
"UndoStack cannot have no children");
return mUndoStack ? mUndoStack->GetSize() : 0;
}
size_t TransactionItem::NumberOfRedoItems() const {
NS_WARNING_ASSERTION(!mRedoStack || mRedoStack->GetSize() > 0,
"UndoStack cannot have no children");
return mRedoStack ? mRedoStack->GetSize() : 0;
}
nsresult TransactionItem::RecoverFromUndoError(
TransactionManager* aTransactionManager) {
// If this method gets called, we never got to the point where we
// successfully called UndoTransaction() for the transaction item itself.
// Just redo any children that successfully called undo!
nsresult rv = RedoChildren(aTransactionManager);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"TransactionItem::RedoChildren() failed");
return rv;
}
nsresult TransactionItem::RecoverFromRedoError(
TransactionManager* aTransactionManager) {
// If this method gets called, we already successfully called
// RedoTransaction() for the transaction item itself. Undo all
// the children that successfully called RedoTransaction(),
// then undo the transaction item itself.
nsresult rv = UndoChildren(aTransactionManager);
if (NS_FAILED(rv)) {
NS_WARNING("TransactionItem::UndoChildren() failed");
return rv;
}
if (!mTransaction) {
return NS_OK;
}
OwningNonNull<nsITransaction> transaction = *mTransaction;
rv = transaction->UndoTransaction();
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"nsITransaction::UndoTransaction() failed");
return rv;
}
} // namespace mozilla