michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/Assertions.h" michael@0: #include "mozilla/mozalloc.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsDebug.h" michael@0: #include "nsError.h" michael@0: #include "nsISupportsBase.h" michael@0: #include "nsISupportsUtils.h" michael@0: #include "nsITransaction.h" michael@0: #include "nsITransactionList.h" michael@0: #include "nsITransactionListener.h" michael@0: #include "nsIWeakReference.h" michael@0: #include "nsTransactionItem.h" michael@0: #include "nsTransactionList.h" michael@0: #include "nsTransactionManager.h" michael@0: #include "nsTransactionStack.h" michael@0: michael@0: nsTransactionManager::nsTransactionManager(int32_t aMaxTransactionCount) michael@0: : mMaxTransactionCount(aMaxTransactionCount) michael@0: , mDoStack(nsTransactionStack::FOR_UNDO) michael@0: , mUndoStack(nsTransactionStack::FOR_UNDO) michael@0: , mRedoStack(nsTransactionStack::FOR_REDO) michael@0: { michael@0: } michael@0: michael@0: nsTransactionManager::~nsTransactionManager() michael@0: { michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(nsTransactionManager) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsTransactionManager) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mListeners) michael@0: tmp->mDoStack.DoUnlink(); michael@0: tmp->mUndoStack.DoUnlink(); michael@0: tmp->mRedoStack.DoUnlink(); michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsTransactionManager) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListeners) michael@0: tmp->mDoStack.DoTraverse(cb); michael@0: tmp->mUndoStack.DoTraverse(cb); michael@0: tmp->mRedoStack.DoTraverse(cb); michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTransactionManager) michael@0: NS_INTERFACE_MAP_ENTRY(nsITransactionManager) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITransactionManager) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTransactionManager) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTransactionManager) michael@0: michael@0: NS_IMETHODIMP michael@0: nsTransactionManager::DoTransaction(nsITransaction *aTransaction) michael@0: { michael@0: nsresult result; michael@0: michael@0: NS_ENSURE_TRUE(aTransaction, NS_ERROR_NULL_POINTER); michael@0: michael@0: bool doInterrupt = false; michael@0: michael@0: result = WillDoNotify(aTransaction, &doInterrupt); michael@0: michael@0: if (NS_FAILED(result)) { michael@0: return result; michael@0: } michael@0: michael@0: if (doInterrupt) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: result = BeginTransaction(aTransaction, nullptr); michael@0: michael@0: if (NS_FAILED(result)) { michael@0: DidDoNotify(aTransaction, result); michael@0: return result; michael@0: } michael@0: michael@0: result = EndTransaction(false); michael@0: michael@0: nsresult result2 = DidDoNotify(aTransaction, result); michael@0: michael@0: if (NS_SUCCEEDED(result)) michael@0: result = result2; michael@0: michael@0: return result; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTransactionManager::UndoTransaction() michael@0: { michael@0: nsresult result = NS_OK; michael@0: michael@0: // It is illegal to call UndoTransaction() while the transaction manager is michael@0: // executing a transaction's DoTransaction() method! If this happens, michael@0: // the UndoTransaction() request is ignored, and we return NS_ERROR_FAILURE. michael@0: michael@0: nsRefPtr tx = mDoStack.Peek(); michael@0: michael@0: if (tx) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Peek at the top of the undo stack. Don't remove the transaction michael@0: // until it has successfully completed. michael@0: tx = mUndoStack.Peek(); michael@0: michael@0: // Bail if there's nothing on the stack. michael@0: if (!tx) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr t = tx->GetTransaction(); michael@0: michael@0: bool doInterrupt = false; michael@0: michael@0: result = WillUndoNotify(t, &doInterrupt); michael@0: michael@0: if (NS_FAILED(result)) { michael@0: return result; michael@0: } michael@0: michael@0: if (doInterrupt) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: result = tx->UndoTransaction(this); michael@0: michael@0: if (NS_SUCCEEDED(result)) { michael@0: tx = mUndoStack.Pop(); michael@0: mRedoStack.Push(tx); michael@0: } michael@0: michael@0: nsresult result2 = DidUndoNotify(t, result); michael@0: michael@0: if (NS_SUCCEEDED(result)) michael@0: result = result2; michael@0: michael@0: return result; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTransactionManager::RedoTransaction() michael@0: { michael@0: nsresult result = NS_OK; michael@0: michael@0: // It is illegal to call RedoTransaction() while the transaction manager is michael@0: // executing a transaction's DoTransaction() method! If this happens, michael@0: // the RedoTransaction() request is ignored, and we return NS_ERROR_FAILURE. michael@0: michael@0: nsRefPtr tx = mDoStack.Peek(); michael@0: michael@0: if (tx) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Peek at the top of the redo stack. Don't remove the transaction michael@0: // until it has successfully completed. michael@0: tx = mRedoStack.Peek(); michael@0: michael@0: // Bail if there's nothing on the stack. michael@0: if (!tx) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr t = tx->GetTransaction(); michael@0: michael@0: bool doInterrupt = false; michael@0: michael@0: result = WillRedoNotify(t, &doInterrupt); michael@0: michael@0: if (NS_FAILED(result)) { michael@0: return result; michael@0: } michael@0: michael@0: if (doInterrupt) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: result = tx->RedoTransaction(this); michael@0: michael@0: if (NS_SUCCEEDED(result)) { michael@0: tx = mRedoStack.Pop(); michael@0: mUndoStack.Push(tx); michael@0: } michael@0: michael@0: nsresult result2 = DidRedoNotify(t, result); michael@0: michael@0: if (NS_SUCCEEDED(result)) michael@0: result = result2; michael@0: michael@0: return result; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTransactionManager::Clear() michael@0: { michael@0: nsresult result; michael@0: michael@0: result = ClearRedoStack(); michael@0: michael@0: if (NS_FAILED(result)) { michael@0: return result; michael@0: } michael@0: michael@0: result = ClearUndoStack(); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTransactionManager::BeginBatch(nsISupports* aData) michael@0: { michael@0: nsresult result; michael@0: michael@0: // We can batch independent transactions together by simply pushing michael@0: // a dummy transaction item on the do stack. This dummy transaction item michael@0: // will be popped off the do stack, and then pushed on the undo stack michael@0: // in EndBatch(). michael@0: michael@0: bool doInterrupt = false; michael@0: michael@0: result = WillBeginBatchNotify(&doInterrupt); michael@0: michael@0: if (NS_FAILED(result)) { michael@0: return result; michael@0: } michael@0: michael@0: if (doInterrupt) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: result = BeginTransaction(0, aData); michael@0: michael@0: nsresult result2 = DidBeginBatchNotify(result); michael@0: michael@0: if (NS_SUCCEEDED(result)) michael@0: result = result2; michael@0: michael@0: return result; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTransactionManager::EndBatch(bool aAllowEmpty) michael@0: { michael@0: nsCOMPtr ti; michael@0: nsresult result; michael@0: michael@0: // XXX: Need to add some mechanism to detect the case where the transaction michael@0: // at the top of the do stack isn't the dummy transaction, so we can michael@0: // throw an error!! This can happen if someone calls EndBatch() within michael@0: // the DoTransaction() method of a transaction. michael@0: // michael@0: // For now, we can detect this case by checking the value of the michael@0: // dummy transaction's mTransaction field. If it is our dummy michael@0: // transaction, it should be nullptr. This may not be true in the michael@0: // future when we allow users to execute a transaction when beginning michael@0: // a batch!!!! michael@0: michael@0: nsRefPtr tx = mDoStack.Peek(); michael@0: michael@0: if (tx) { michael@0: ti = tx->GetTransaction(); michael@0: } michael@0: michael@0: if (!tx || ti) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: bool doInterrupt = false; michael@0: michael@0: result = WillEndBatchNotify(&doInterrupt); michael@0: michael@0: if (NS_FAILED(result)) { michael@0: return result; michael@0: } michael@0: michael@0: if (doInterrupt) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: result = EndTransaction(aAllowEmpty); michael@0: michael@0: nsresult result2 = DidEndBatchNotify(result); michael@0: michael@0: if (NS_SUCCEEDED(result)) michael@0: result = result2; michael@0: michael@0: return result; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTransactionManager::GetNumberOfUndoItems(int32_t *aNumItems) michael@0: { michael@0: *aNumItems = mUndoStack.GetSize(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTransactionManager::GetNumberOfRedoItems(int32_t *aNumItems) michael@0: { michael@0: *aNumItems = mRedoStack.GetSize(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTransactionManager::GetMaxTransactionCount(int32_t *aMaxCount) michael@0: { michael@0: NS_ENSURE_TRUE(aMaxCount, NS_ERROR_NULL_POINTER); michael@0: michael@0: *aMaxCount = mMaxTransactionCount; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTransactionManager::SetMaxTransactionCount(int32_t aMaxCount) michael@0: { michael@0: int32_t numUndoItems = 0, numRedoItems = 0, total = 0; michael@0: michael@0: // It is illegal to call SetMaxTransactionCount() while the transaction michael@0: // manager is executing a transaction's DoTransaction() method because michael@0: // the undo and redo stacks might get pruned! If this happens, the michael@0: // SetMaxTransactionCount() request is ignored, and we return michael@0: // NS_ERROR_FAILURE. michael@0: michael@0: nsRefPtr tx = mDoStack.Peek(); michael@0: michael@0: if (tx) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // If aMaxCount is less than zero, the user wants unlimited michael@0: // levels of undo! No need to prune the undo or redo stacks! michael@0: michael@0: if (aMaxCount < 0) { michael@0: mMaxTransactionCount = -1; michael@0: return NS_OK; michael@0: } michael@0: michael@0: numUndoItems = mUndoStack.GetSize(); michael@0: michael@0: numRedoItems = mRedoStack.GetSize(); michael@0: michael@0: total = numUndoItems + numRedoItems; michael@0: michael@0: // If aMaxCount is greater than the number of transactions that currently michael@0: // exist on the undo and redo stack, there is no need to prune the michael@0: // undo or redo stacks! michael@0: michael@0: if (aMaxCount > total ) { michael@0: mMaxTransactionCount = aMaxCount; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Try getting rid of some transactions on the undo stack! Start at michael@0: // the bottom of the stack and pop towards the top. michael@0: michael@0: while (numUndoItems > 0 && (numRedoItems + numUndoItems) > aMaxCount) { michael@0: tx = mUndoStack.PopBottom(); michael@0: michael@0: if (!tx) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: --numUndoItems; michael@0: } michael@0: michael@0: // If necessary, get rid of some transactions on the redo stack! Start at michael@0: // the bottom of the stack and pop towards the top. michael@0: michael@0: while (numRedoItems > 0 && (numRedoItems + numUndoItems) > aMaxCount) { michael@0: tx = mRedoStack.PopBottom(); michael@0: michael@0: if (!tx) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: --numRedoItems; michael@0: } michael@0: michael@0: mMaxTransactionCount = aMaxCount; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTransactionManager::PeekUndoStack(nsITransaction **aTransaction) michael@0: { michael@0: MOZ_ASSERT(aTransaction); michael@0: *aTransaction = PeekUndoStack().take(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsTransactionManager::PeekUndoStack() michael@0: { michael@0: nsRefPtr tx = mUndoStack.Peek(); michael@0: michael@0: if (!tx) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return tx->GetTransaction(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTransactionManager::PeekRedoStack(nsITransaction** aTransaction) michael@0: { michael@0: MOZ_ASSERT(aTransaction); michael@0: *aTransaction = PeekRedoStack().take(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsTransactionManager::PeekRedoStack() michael@0: { michael@0: nsRefPtr tx = mRedoStack.Peek(); michael@0: michael@0: if (!tx) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return tx->GetTransaction(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTransactionManager::GetUndoList(nsITransactionList **aTransactionList) michael@0: { michael@0: NS_ENSURE_TRUE(aTransactionList, NS_ERROR_NULL_POINTER); michael@0: michael@0: *aTransactionList = (nsITransactionList *)new nsTransactionList(this, &mUndoStack); michael@0: michael@0: NS_IF_ADDREF(*aTransactionList); michael@0: michael@0: return (! *aTransactionList) ? NS_ERROR_OUT_OF_MEMORY : NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTransactionManager::GetRedoList(nsITransactionList **aTransactionList) michael@0: { michael@0: NS_ENSURE_TRUE(aTransactionList, NS_ERROR_NULL_POINTER); michael@0: michael@0: *aTransactionList = (nsITransactionList *)new nsTransactionList(this, &mRedoStack); michael@0: michael@0: NS_IF_ADDREF(*aTransactionList); michael@0: michael@0: return (! *aTransactionList) ? NS_ERROR_OUT_OF_MEMORY : NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTransactionManager::BatchTopUndo() michael@0: { michael@0: if (mUndoStack.GetSize() < 2) { michael@0: // Not enough transactions to merge into one batch. michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsRefPtr lastUndo; michael@0: nsRefPtr previousUndo; michael@0: michael@0: lastUndo = mUndoStack.Pop(); michael@0: MOZ_ASSERT(lastUndo, "There should be at least two transactions."); michael@0: michael@0: previousUndo = mUndoStack.Peek(); michael@0: MOZ_ASSERT(previousUndo, "There should be at least two transactions."); michael@0: michael@0: nsresult result = previousUndo->AddChild(lastUndo); michael@0: michael@0: // Transfer data from the transactions that is going to be michael@0: // merged to the transaction that it is being merged with. michael@0: nsCOMArray& lastData = lastUndo->GetData(); michael@0: nsCOMArray& previousData = previousUndo->GetData(); michael@0: NS_ENSURE_TRUE(previousData.AppendObjects(lastData), NS_ERROR_UNEXPECTED); michael@0: lastData.Clear(); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: nsTransactionManager::RemoveTopUndo() michael@0: { michael@0: nsRefPtr lastUndo; michael@0: michael@0: lastUndo = mUndoStack.Peek(); michael@0: if (!lastUndo) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: lastUndo = mUndoStack.Pop(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTransactionManager::AddListener(nsITransactionListener *aListener) michael@0: { michael@0: NS_ENSURE_TRUE(aListener, NS_ERROR_NULL_POINTER); michael@0: michael@0: return mListeners.AppendObject(aListener) ? NS_OK : NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTransactionManager::RemoveListener(nsITransactionListener *aListener) michael@0: { michael@0: NS_ENSURE_TRUE(aListener, NS_ERROR_NULL_POINTER); michael@0: michael@0: return mListeners.RemoveObject(aListener) ? NS_OK : NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTransactionManager::ClearUndoStack() michael@0: { michael@0: mUndoStack.Clear(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTransactionManager::ClearRedoStack() michael@0: { michael@0: mRedoStack.Clear(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTransactionManager::WillDoNotify(nsITransaction *aTransaction, bool *aInterrupt) michael@0: { michael@0: nsresult result = NS_OK; michael@0: for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) michael@0: { michael@0: nsITransactionListener *listener = mListeners[i]; michael@0: michael@0: NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE); michael@0: michael@0: result = listener->WillDo(this, aTransaction, aInterrupt); michael@0: michael@0: if (NS_FAILED(result) || *aInterrupt) michael@0: break; michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: nsTransactionManager::DidDoNotify(nsITransaction *aTransaction, nsresult aDoResult) michael@0: { michael@0: nsresult result = NS_OK; michael@0: for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) michael@0: { michael@0: nsITransactionListener *listener = mListeners[i]; michael@0: michael@0: NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE); michael@0: michael@0: result = listener->DidDo(this, aTransaction, aDoResult); michael@0: michael@0: if (NS_FAILED(result)) michael@0: break; michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: nsTransactionManager::WillUndoNotify(nsITransaction *aTransaction, bool *aInterrupt) michael@0: { michael@0: nsresult result = NS_OK; michael@0: for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) michael@0: { michael@0: nsITransactionListener *listener = mListeners[i]; michael@0: michael@0: NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE); michael@0: michael@0: result = listener->WillUndo(this, aTransaction, aInterrupt); michael@0: michael@0: if (NS_FAILED(result) || *aInterrupt) michael@0: break; michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: nsTransactionManager::DidUndoNotify(nsITransaction *aTransaction, nsresult aUndoResult) michael@0: { michael@0: nsresult result = NS_OK; michael@0: for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) michael@0: { michael@0: nsITransactionListener *listener = mListeners[i]; michael@0: michael@0: NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE); michael@0: michael@0: result = listener->DidUndo(this, aTransaction, aUndoResult); michael@0: michael@0: if (NS_FAILED(result)) michael@0: break; michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: nsTransactionManager::WillRedoNotify(nsITransaction *aTransaction, bool *aInterrupt) michael@0: { michael@0: nsresult result = NS_OK; michael@0: for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) michael@0: { michael@0: nsITransactionListener *listener = mListeners[i]; michael@0: michael@0: NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE); michael@0: michael@0: result = listener->WillRedo(this, aTransaction, aInterrupt); michael@0: michael@0: if (NS_FAILED(result) || *aInterrupt) michael@0: break; michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: nsTransactionManager::DidRedoNotify(nsITransaction *aTransaction, nsresult aRedoResult) michael@0: { michael@0: nsresult result = NS_OK; michael@0: for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) michael@0: { michael@0: nsITransactionListener *listener = mListeners[i]; michael@0: michael@0: NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE); michael@0: michael@0: result = listener->DidRedo(this, aTransaction, aRedoResult); michael@0: michael@0: if (NS_FAILED(result)) michael@0: break; michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: nsTransactionManager::WillBeginBatchNotify(bool *aInterrupt) michael@0: { michael@0: nsresult result = NS_OK; michael@0: for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) michael@0: { michael@0: nsITransactionListener *listener = mListeners[i]; michael@0: michael@0: NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE); michael@0: michael@0: result = listener->WillBeginBatch(this, aInterrupt); michael@0: michael@0: if (NS_FAILED(result) || *aInterrupt) michael@0: break; michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: nsTransactionManager::DidBeginBatchNotify(nsresult aResult) michael@0: { michael@0: nsresult result = NS_OK; michael@0: for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) michael@0: { michael@0: nsITransactionListener *listener = mListeners[i]; michael@0: michael@0: NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE); michael@0: michael@0: result = listener->DidBeginBatch(this, aResult); michael@0: michael@0: if (NS_FAILED(result)) michael@0: break; michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: nsTransactionManager::WillEndBatchNotify(bool *aInterrupt) michael@0: { michael@0: nsresult result = NS_OK; michael@0: for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) michael@0: { michael@0: nsITransactionListener *listener = mListeners[i]; michael@0: michael@0: NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE); michael@0: michael@0: result = listener->WillEndBatch(this, aInterrupt); michael@0: michael@0: if (NS_FAILED(result) || *aInterrupt) michael@0: break; michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: nsTransactionManager::DidEndBatchNotify(nsresult aResult) michael@0: { michael@0: nsresult result = NS_OK; michael@0: for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) michael@0: { michael@0: nsITransactionListener *listener = mListeners[i]; michael@0: michael@0: NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE); michael@0: michael@0: result = listener->DidEndBatch(this, aResult); michael@0: michael@0: if (NS_FAILED(result)) michael@0: break; michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: nsTransactionManager::WillMergeNotify(nsITransaction *aTop, nsITransaction *aTransaction, bool *aInterrupt) michael@0: { michael@0: nsresult result = NS_OK; michael@0: for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) michael@0: { michael@0: nsITransactionListener *listener = mListeners[i]; michael@0: michael@0: NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE); michael@0: michael@0: result = listener->WillMerge(this, aTop, aTransaction, aInterrupt); michael@0: michael@0: if (NS_FAILED(result) || *aInterrupt) michael@0: break; michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: nsTransactionManager::DidMergeNotify(nsITransaction *aTop, michael@0: nsITransaction *aTransaction, michael@0: bool aDidMerge, michael@0: nsresult aMergeResult) michael@0: { michael@0: nsresult result = NS_OK; michael@0: for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) michael@0: { michael@0: nsITransactionListener *listener = mListeners[i]; michael@0: michael@0: NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE); michael@0: michael@0: result = listener->DidMerge(this, aTop, aTransaction, aDidMerge, aMergeResult); michael@0: michael@0: if (NS_FAILED(result)) michael@0: break; michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: nsTransactionManager::BeginTransaction(nsITransaction *aTransaction, michael@0: nsISupports *aData) michael@0: { michael@0: nsresult result = NS_OK; michael@0: michael@0: // XXX: POSSIBLE OPTIMIZATION michael@0: // We could use a factory that pre-allocates/recycles transaction items. michael@0: nsRefPtr tx = new nsTransactionItem(aTransaction); michael@0: michael@0: if (aData) { michael@0: nsCOMArray& data = tx->GetData(); michael@0: data.AppendObject(aData); michael@0: } michael@0: michael@0: if (!tx) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: mDoStack.Push(tx); michael@0: michael@0: result = tx->DoTransaction(); michael@0: michael@0: if (NS_FAILED(result)) { michael@0: tx = mDoStack.Pop(); michael@0: return result; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTransactionManager::EndTransaction(bool aAllowEmpty) michael@0: { michael@0: nsresult result = NS_OK; michael@0: michael@0: nsRefPtr tx = mDoStack.Pop(); michael@0: michael@0: if (!tx) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMPtr tint = tx->GetTransaction(); michael@0: michael@0: if (!tint && !aAllowEmpty) { michael@0: int32_t nc = 0; michael@0: michael@0: // If we get here, the transaction must be a dummy batch transaction michael@0: // created by BeginBatch(). If it contains no children, get rid of it! michael@0: michael@0: tx->GetNumberOfChildren(&nc); michael@0: michael@0: if (!nc) { michael@0: return result; michael@0: } michael@0: } michael@0: michael@0: // Check if the transaction is transient. If it is, there's nothing michael@0: // more to do, just return. michael@0: michael@0: bool isTransient = false; michael@0: michael@0: if (tint) michael@0: result = tint->GetIsTransient(&isTransient); michael@0: michael@0: if (NS_FAILED(result) || isTransient || !mMaxTransactionCount) { michael@0: // XXX: Should we be clearing the redo stack if the transaction michael@0: // is transient and there is nothing on the do stack? michael@0: return result; michael@0: } michael@0: michael@0: // Check if there is a transaction on the do stack. If there is, michael@0: // the current transaction is a "sub" transaction, and should michael@0: // be added to the transaction at the top of the do stack. michael@0: michael@0: nsRefPtr top = mDoStack.Peek(); michael@0: if (top) { michael@0: result = top->AddChild(tx); michael@0: michael@0: // XXX: What do we do if this fails? michael@0: michael@0: return result; michael@0: } michael@0: michael@0: // The transaction succeeded, so clear the redo stack. michael@0: michael@0: result = ClearRedoStack(); michael@0: michael@0: if (NS_FAILED(result)) { michael@0: // XXX: What do we do if this fails? michael@0: } michael@0: michael@0: // Check if we can coalesce this transaction with the one at the top michael@0: // of the undo stack. michael@0: michael@0: top = mUndoStack.Peek(); michael@0: michael@0: if (tint && top) { michael@0: bool didMerge = false; michael@0: nsCOMPtr topTransaction = top->GetTransaction(); michael@0: michael@0: if (topTransaction) { michael@0: michael@0: bool doInterrupt = false; michael@0: michael@0: result = WillMergeNotify(topTransaction, tint, &doInterrupt); michael@0: michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: if (!doInterrupt) { michael@0: result = topTransaction->Merge(tint, &didMerge); michael@0: michael@0: nsresult result2 = DidMergeNotify(topTransaction, tint, didMerge, result); michael@0: michael@0: if (NS_SUCCEEDED(result)) michael@0: result = result2; michael@0: michael@0: if (NS_FAILED(result)) { michael@0: // XXX: What do we do if this fails? michael@0: } michael@0: michael@0: if (didMerge) { michael@0: return result; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Check to see if we've hit the max level of undo. If so, michael@0: // pop the bottom transaction off the undo stack and release it! michael@0: michael@0: int32_t sz = mUndoStack.GetSize(); michael@0: michael@0: if (mMaxTransactionCount > 0 && sz >= mMaxTransactionCount) { michael@0: nsRefPtr overflow = mUndoStack.PopBottom(); michael@0: } michael@0: michael@0: // Push the transaction on the undo stack: michael@0: michael@0: mUndoStack.Push(tx); michael@0: michael@0: return NS_OK; michael@0: } michael@0: