1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "mozilla/Assertions.h"
7 #include "mozilla/mozalloc.h"
12 #include "nsISupportsBase.h"
13 #include "nsISupportsUtils.h"
14 #include "nsITransaction.h"
15 #include "nsITransactionList.h"
16 #include "nsITransactionListener.h"
17 #include "nsIWeakReference.h"
18 #include "nsTransactionItem.h"
19 #include "nsTransactionList.h"
20 #include "nsTransactionManager.h"
21 #include "nsTransactionStack.h"
23 nsTransactionManager::nsTransactionManager(int32_t aMaxTransactionCount
)
24 : mMaxTransactionCount(aMaxTransactionCount
)
25 , mDoStack(nsTransactionStack::FOR_UNDO
)
26 , mUndoStack(nsTransactionStack::FOR_UNDO
)
27 , mRedoStack(nsTransactionStack::FOR_REDO
)
31 nsTransactionManager::~nsTransactionManager()
35 NS_IMPL_CYCLE_COLLECTION_CLASS(nsTransactionManager
)
37 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsTransactionManager
)
38 NS_IMPL_CYCLE_COLLECTION_UNLINK(mListeners
)
39 tmp
->mDoStack
.DoUnlink();
40 tmp
->mUndoStack
.DoUnlink();
41 tmp
->mRedoStack
.DoUnlink();
42 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
44 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsTransactionManager
)
45 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListeners
)
46 tmp
->mDoStack
.DoTraverse(cb
);
47 tmp
->mUndoStack
.DoTraverse(cb
);
48 tmp
->mRedoStack
.DoTraverse(cb
);
49 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
51 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTransactionManager
)
52 NS_INTERFACE_MAP_ENTRY(nsITransactionManager
)
53 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference
)
54 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsITransactionManager
)
57 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTransactionManager
)
58 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTransactionManager
)
61 nsTransactionManager::DoTransaction(nsITransaction
*aTransaction
)
65 NS_ENSURE_TRUE(aTransaction
, NS_ERROR_NULL_POINTER
);
67 bool doInterrupt
= false;
69 result
= WillDoNotify(aTransaction
, &doInterrupt
);
71 if (NS_FAILED(result
)) {
79 result
= BeginTransaction(aTransaction
, nullptr);
81 if (NS_FAILED(result
)) {
82 DidDoNotify(aTransaction
, result
);
86 result
= EndTransaction(false);
88 nsresult result2
= DidDoNotify(aTransaction
, result
);
90 if (NS_SUCCEEDED(result
))
97 nsTransactionManager::UndoTransaction()
99 nsresult result
= NS_OK
;
101 // It is illegal to call UndoTransaction() while the transaction manager is
102 // executing a transaction's DoTransaction() method! If this happens,
103 // the UndoTransaction() request is ignored, and we return NS_ERROR_FAILURE.
105 nsRefPtr
<nsTransactionItem
> tx
= mDoStack
.Peek();
108 return NS_ERROR_FAILURE
;
111 // Peek at the top of the undo stack. Don't remove the transaction
112 // until it has successfully completed.
113 tx
= mUndoStack
.Peek();
115 // Bail if there's nothing on the stack.
120 nsCOMPtr
<nsITransaction
> t
= tx
->GetTransaction();
122 bool doInterrupt
= false;
124 result
= WillUndoNotify(t
, &doInterrupt
);
126 if (NS_FAILED(result
)) {
134 result
= tx
->UndoTransaction(this);
136 if (NS_SUCCEEDED(result
)) {
137 tx
= mUndoStack
.Pop();
141 nsresult result2
= DidUndoNotify(t
, result
);
143 if (NS_SUCCEEDED(result
))
150 nsTransactionManager::RedoTransaction()
152 nsresult result
= NS_OK
;
154 // It is illegal to call RedoTransaction() while the transaction manager is
155 // executing a transaction's DoTransaction() method! If this happens,
156 // the RedoTransaction() request is ignored, and we return NS_ERROR_FAILURE.
158 nsRefPtr
<nsTransactionItem
> tx
= mDoStack
.Peek();
161 return NS_ERROR_FAILURE
;
164 // Peek at the top of the redo stack. Don't remove the transaction
165 // until it has successfully completed.
166 tx
= mRedoStack
.Peek();
168 // Bail if there's nothing on the stack.
173 nsCOMPtr
<nsITransaction
> t
= tx
->GetTransaction();
175 bool doInterrupt
= false;
177 result
= WillRedoNotify(t
, &doInterrupt
);
179 if (NS_FAILED(result
)) {
187 result
= tx
->RedoTransaction(this);
189 if (NS_SUCCEEDED(result
)) {
190 tx
= mRedoStack
.Pop();
194 nsresult result2
= DidRedoNotify(t
, result
);
196 if (NS_SUCCEEDED(result
))
203 nsTransactionManager::Clear()
207 result
= ClearRedoStack();
209 if (NS_FAILED(result
)) {
213 result
= ClearUndoStack();
219 nsTransactionManager::BeginBatch(nsISupports
* aData
)
223 // We can batch independent transactions together by simply pushing
224 // a dummy transaction item on the do stack. This dummy transaction item
225 // will be popped off the do stack, and then pushed on the undo stack
228 bool doInterrupt
= false;
230 result
= WillBeginBatchNotify(&doInterrupt
);
232 if (NS_FAILED(result
)) {
240 result
= BeginTransaction(0, aData
);
242 nsresult result2
= DidBeginBatchNotify(result
);
244 if (NS_SUCCEEDED(result
))
251 nsTransactionManager::EndBatch(bool aAllowEmpty
)
253 nsCOMPtr
<nsITransaction
> ti
;
256 // XXX: Need to add some mechanism to detect the case where the transaction
257 // at the top of the do stack isn't the dummy transaction, so we can
258 // throw an error!! This can happen if someone calls EndBatch() within
259 // the DoTransaction() method of a transaction.
261 // For now, we can detect this case by checking the value of the
262 // dummy transaction's mTransaction field. If it is our dummy
263 // transaction, it should be nullptr. This may not be true in the
264 // future when we allow users to execute a transaction when beginning
267 nsRefPtr
<nsTransactionItem
> tx
= mDoStack
.Peek();
270 ti
= tx
->GetTransaction();
274 return NS_ERROR_FAILURE
;
277 bool doInterrupt
= false;
279 result
= WillEndBatchNotify(&doInterrupt
);
281 if (NS_FAILED(result
)) {
289 result
= EndTransaction(aAllowEmpty
);
291 nsresult result2
= DidEndBatchNotify(result
);
293 if (NS_SUCCEEDED(result
))
300 nsTransactionManager::GetNumberOfUndoItems(int32_t *aNumItems
)
302 *aNumItems
= mUndoStack
.GetSize();
307 nsTransactionManager::GetNumberOfRedoItems(int32_t *aNumItems
)
309 *aNumItems
= mRedoStack
.GetSize();
314 nsTransactionManager::GetMaxTransactionCount(int32_t *aMaxCount
)
316 NS_ENSURE_TRUE(aMaxCount
, NS_ERROR_NULL_POINTER
);
318 *aMaxCount
= mMaxTransactionCount
;
324 nsTransactionManager::SetMaxTransactionCount(int32_t aMaxCount
)
326 int32_t numUndoItems
= 0, numRedoItems
= 0, total
= 0;
328 // It is illegal to call SetMaxTransactionCount() while the transaction
329 // manager is executing a transaction's DoTransaction() method because
330 // the undo and redo stacks might get pruned! If this happens, the
331 // SetMaxTransactionCount() request is ignored, and we return
334 nsRefPtr
<nsTransactionItem
> tx
= mDoStack
.Peek();
337 return NS_ERROR_FAILURE
;
340 // If aMaxCount is less than zero, the user wants unlimited
341 // levels of undo! No need to prune the undo or redo stacks!
344 mMaxTransactionCount
= -1;
348 numUndoItems
= mUndoStack
.GetSize();
350 numRedoItems
= mRedoStack
.GetSize();
352 total
= numUndoItems
+ numRedoItems
;
354 // If aMaxCount is greater than the number of transactions that currently
355 // exist on the undo and redo stack, there is no need to prune the
356 // undo or redo stacks!
358 if (aMaxCount
> total
) {
359 mMaxTransactionCount
= aMaxCount
;
363 // Try getting rid of some transactions on the undo stack! Start at
364 // the bottom of the stack and pop towards the top.
366 while (numUndoItems
> 0 && (numRedoItems
+ numUndoItems
) > aMaxCount
) {
367 tx
= mUndoStack
.PopBottom();
370 return NS_ERROR_FAILURE
;
376 // If necessary, get rid of some transactions on the redo stack! Start at
377 // the bottom of the stack and pop towards the top.
379 while (numRedoItems
> 0 && (numRedoItems
+ numUndoItems
) > aMaxCount
) {
380 tx
= mRedoStack
.PopBottom();
383 return NS_ERROR_FAILURE
;
389 mMaxTransactionCount
= aMaxCount
;
395 nsTransactionManager::PeekUndoStack(nsITransaction
**aTransaction
)
397 MOZ_ASSERT(aTransaction
);
398 *aTransaction
= PeekUndoStack().take();
402 already_AddRefed
<nsITransaction
>
403 nsTransactionManager::PeekUndoStack()
405 nsRefPtr
<nsTransactionItem
> tx
= mUndoStack
.Peek();
411 return tx
->GetTransaction();
415 nsTransactionManager::PeekRedoStack(nsITransaction
** aTransaction
)
417 MOZ_ASSERT(aTransaction
);
418 *aTransaction
= PeekRedoStack().take();
422 already_AddRefed
<nsITransaction
>
423 nsTransactionManager::PeekRedoStack()
425 nsRefPtr
<nsTransactionItem
> tx
= mRedoStack
.Peek();
431 return tx
->GetTransaction();
435 nsTransactionManager::GetUndoList(nsITransactionList
**aTransactionList
)
437 NS_ENSURE_TRUE(aTransactionList
, NS_ERROR_NULL_POINTER
);
439 *aTransactionList
= (nsITransactionList
*)new nsTransactionList(this, &mUndoStack
);
441 NS_IF_ADDREF(*aTransactionList
);
443 return (! *aTransactionList
) ? NS_ERROR_OUT_OF_MEMORY
: NS_OK
;
447 nsTransactionManager::GetRedoList(nsITransactionList
**aTransactionList
)
449 NS_ENSURE_TRUE(aTransactionList
, NS_ERROR_NULL_POINTER
);
451 *aTransactionList
= (nsITransactionList
*)new nsTransactionList(this, &mRedoStack
);
453 NS_IF_ADDREF(*aTransactionList
);
455 return (! *aTransactionList
) ? NS_ERROR_OUT_OF_MEMORY
: NS_OK
;
459 nsTransactionManager::BatchTopUndo()
461 if (mUndoStack
.GetSize() < 2) {
462 // Not enough transactions to merge into one batch.
466 nsRefPtr
<nsTransactionItem
> lastUndo
;
467 nsRefPtr
<nsTransactionItem
> previousUndo
;
469 lastUndo
= mUndoStack
.Pop();
470 MOZ_ASSERT(lastUndo
, "There should be at least two transactions.");
472 previousUndo
= mUndoStack
.Peek();
473 MOZ_ASSERT(previousUndo
, "There should be at least two transactions.");
475 nsresult result
= previousUndo
->AddChild(lastUndo
);
477 // Transfer data from the transactions that is going to be
478 // merged to the transaction that it is being merged with.
479 nsCOMArray
<nsISupports
>& lastData
= lastUndo
->GetData();
480 nsCOMArray
<nsISupports
>& previousData
= previousUndo
->GetData();
481 NS_ENSURE_TRUE(previousData
.AppendObjects(lastData
), NS_ERROR_UNEXPECTED
);
488 nsTransactionManager::RemoveTopUndo()
490 nsRefPtr
<nsTransactionItem
> lastUndo
;
492 lastUndo
= mUndoStack
.Peek();
497 lastUndo
= mUndoStack
.Pop();
503 nsTransactionManager::AddListener(nsITransactionListener
*aListener
)
505 NS_ENSURE_TRUE(aListener
, NS_ERROR_NULL_POINTER
);
507 return mListeners
.AppendObject(aListener
) ? NS_OK
: NS_ERROR_FAILURE
;
511 nsTransactionManager::RemoveListener(nsITransactionListener
*aListener
)
513 NS_ENSURE_TRUE(aListener
, NS_ERROR_NULL_POINTER
);
515 return mListeners
.RemoveObject(aListener
) ? NS_OK
: NS_ERROR_FAILURE
;
519 nsTransactionManager::ClearUndoStack()
526 nsTransactionManager::ClearRedoStack()
533 nsTransactionManager::WillDoNotify(nsITransaction
*aTransaction
, bool *aInterrupt
)
535 nsresult result
= NS_OK
;
536 for (int32_t i
= 0, lcount
= mListeners
.Count(); i
< lcount
; i
++)
538 nsITransactionListener
*listener
= mListeners
[i
];
540 NS_ENSURE_TRUE(listener
, NS_ERROR_FAILURE
);
542 result
= listener
->WillDo(this, aTransaction
, aInterrupt
);
544 if (NS_FAILED(result
) || *aInterrupt
)
552 nsTransactionManager::DidDoNotify(nsITransaction
*aTransaction
, nsresult aDoResult
)
554 nsresult result
= NS_OK
;
555 for (int32_t i
= 0, lcount
= mListeners
.Count(); i
< lcount
; i
++)
557 nsITransactionListener
*listener
= mListeners
[i
];
559 NS_ENSURE_TRUE(listener
, NS_ERROR_FAILURE
);
561 result
= listener
->DidDo(this, aTransaction
, aDoResult
);
563 if (NS_FAILED(result
))
571 nsTransactionManager::WillUndoNotify(nsITransaction
*aTransaction
, bool *aInterrupt
)
573 nsresult result
= NS_OK
;
574 for (int32_t i
= 0, lcount
= mListeners
.Count(); i
< lcount
; i
++)
576 nsITransactionListener
*listener
= mListeners
[i
];
578 NS_ENSURE_TRUE(listener
, NS_ERROR_FAILURE
);
580 result
= listener
->WillUndo(this, aTransaction
, aInterrupt
);
582 if (NS_FAILED(result
) || *aInterrupt
)
590 nsTransactionManager::DidUndoNotify(nsITransaction
*aTransaction
, nsresult aUndoResult
)
592 nsresult result
= NS_OK
;
593 for (int32_t i
= 0, lcount
= mListeners
.Count(); i
< lcount
; i
++)
595 nsITransactionListener
*listener
= mListeners
[i
];
597 NS_ENSURE_TRUE(listener
, NS_ERROR_FAILURE
);
599 result
= listener
->DidUndo(this, aTransaction
, aUndoResult
);
601 if (NS_FAILED(result
))
609 nsTransactionManager::WillRedoNotify(nsITransaction
*aTransaction
, bool *aInterrupt
)
611 nsresult result
= NS_OK
;
612 for (int32_t i
= 0, lcount
= mListeners
.Count(); i
< lcount
; i
++)
614 nsITransactionListener
*listener
= mListeners
[i
];
616 NS_ENSURE_TRUE(listener
, NS_ERROR_FAILURE
);
618 result
= listener
->WillRedo(this, aTransaction
, aInterrupt
);
620 if (NS_FAILED(result
) || *aInterrupt
)
628 nsTransactionManager::DidRedoNotify(nsITransaction
*aTransaction
, nsresult aRedoResult
)
630 nsresult result
= NS_OK
;
631 for (int32_t i
= 0, lcount
= mListeners
.Count(); i
< lcount
; i
++)
633 nsITransactionListener
*listener
= mListeners
[i
];
635 NS_ENSURE_TRUE(listener
, NS_ERROR_FAILURE
);
637 result
= listener
->DidRedo(this, aTransaction
, aRedoResult
);
639 if (NS_FAILED(result
))
647 nsTransactionManager::WillBeginBatchNotify(bool *aInterrupt
)
649 nsresult result
= NS_OK
;
650 for (int32_t i
= 0, lcount
= mListeners
.Count(); i
< lcount
; i
++)
652 nsITransactionListener
*listener
= mListeners
[i
];
654 NS_ENSURE_TRUE(listener
, NS_ERROR_FAILURE
);
656 result
= listener
->WillBeginBatch(this, aInterrupt
);
658 if (NS_FAILED(result
) || *aInterrupt
)
666 nsTransactionManager::DidBeginBatchNotify(nsresult aResult
)
668 nsresult result
= NS_OK
;
669 for (int32_t i
= 0, lcount
= mListeners
.Count(); i
< lcount
; i
++)
671 nsITransactionListener
*listener
= mListeners
[i
];
673 NS_ENSURE_TRUE(listener
, NS_ERROR_FAILURE
);
675 result
= listener
->DidBeginBatch(this, aResult
);
677 if (NS_FAILED(result
))
685 nsTransactionManager::WillEndBatchNotify(bool *aInterrupt
)
687 nsresult result
= NS_OK
;
688 for (int32_t i
= 0, lcount
= mListeners
.Count(); i
< lcount
; i
++)
690 nsITransactionListener
*listener
= mListeners
[i
];
692 NS_ENSURE_TRUE(listener
, NS_ERROR_FAILURE
);
694 result
= listener
->WillEndBatch(this, aInterrupt
);
696 if (NS_FAILED(result
) || *aInterrupt
)
704 nsTransactionManager::DidEndBatchNotify(nsresult aResult
)
706 nsresult result
= NS_OK
;
707 for (int32_t i
= 0, lcount
= mListeners
.Count(); i
< lcount
; i
++)
709 nsITransactionListener
*listener
= mListeners
[i
];
711 NS_ENSURE_TRUE(listener
, NS_ERROR_FAILURE
);
713 result
= listener
->DidEndBatch(this, aResult
);
715 if (NS_FAILED(result
))
723 nsTransactionManager::WillMergeNotify(nsITransaction
*aTop
, nsITransaction
*aTransaction
, bool *aInterrupt
)
725 nsresult result
= NS_OK
;
726 for (int32_t i
= 0, lcount
= mListeners
.Count(); i
< lcount
; i
++)
728 nsITransactionListener
*listener
= mListeners
[i
];
730 NS_ENSURE_TRUE(listener
, NS_ERROR_FAILURE
);
732 result
= listener
->WillMerge(this, aTop
, aTransaction
, aInterrupt
);
734 if (NS_FAILED(result
) || *aInterrupt
)
742 nsTransactionManager::DidMergeNotify(nsITransaction
*aTop
,
743 nsITransaction
*aTransaction
,
745 nsresult aMergeResult
)
747 nsresult result
= NS_OK
;
748 for (int32_t i
= 0, lcount
= mListeners
.Count(); i
< lcount
; i
++)
750 nsITransactionListener
*listener
= mListeners
[i
];
752 NS_ENSURE_TRUE(listener
, NS_ERROR_FAILURE
);
754 result
= listener
->DidMerge(this, aTop
, aTransaction
, aDidMerge
, aMergeResult
);
756 if (NS_FAILED(result
))
764 nsTransactionManager::BeginTransaction(nsITransaction
*aTransaction
,
767 nsresult result
= NS_OK
;
769 // XXX: POSSIBLE OPTIMIZATION
770 // We could use a factory that pre-allocates/recycles transaction items.
771 nsRefPtr
<nsTransactionItem
> tx
= new nsTransactionItem(aTransaction
);
774 nsCOMArray
<nsISupports
>& data
= tx
->GetData();
775 data
.AppendObject(aData
);
779 return NS_ERROR_OUT_OF_MEMORY
;
784 result
= tx
->DoTransaction();
786 if (NS_FAILED(result
)) {
795 nsTransactionManager::EndTransaction(bool aAllowEmpty
)
797 nsresult result
= NS_OK
;
799 nsRefPtr
<nsTransactionItem
> tx
= mDoStack
.Pop();
802 return NS_ERROR_FAILURE
;
804 nsCOMPtr
<nsITransaction
> tint
= tx
->GetTransaction();
806 if (!tint
&& !aAllowEmpty
) {
809 // If we get here, the transaction must be a dummy batch transaction
810 // created by BeginBatch(). If it contains no children, get rid of it!
812 tx
->GetNumberOfChildren(&nc
);
819 // Check if the transaction is transient. If it is, there's nothing
820 // more to do, just return.
822 bool isTransient
= false;
825 result
= tint
->GetIsTransient(&isTransient
);
827 if (NS_FAILED(result
) || isTransient
|| !mMaxTransactionCount
) {
828 // XXX: Should we be clearing the redo stack if the transaction
829 // is transient and there is nothing on the do stack?
833 // Check if there is a transaction on the do stack. If there is,
834 // the current transaction is a "sub" transaction, and should
835 // be added to the transaction at the top of the do stack.
837 nsRefPtr
<nsTransactionItem
> top
= mDoStack
.Peek();
839 result
= top
->AddChild(tx
);
841 // XXX: What do we do if this fails?
846 // The transaction succeeded, so clear the redo stack.
848 result
= ClearRedoStack();
850 if (NS_FAILED(result
)) {
851 // XXX: What do we do if this fails?
854 // Check if we can coalesce this transaction with the one at the top
855 // of the undo stack.
857 top
= mUndoStack
.Peek();
860 bool didMerge
= false;
861 nsCOMPtr
<nsITransaction
> topTransaction
= top
->GetTransaction();
863 if (topTransaction
) {
865 bool doInterrupt
= false;
867 result
= WillMergeNotify(topTransaction
, tint
, &doInterrupt
);
869 NS_ENSURE_SUCCESS(result
, result
);
872 result
= topTransaction
->Merge(tint
, &didMerge
);
874 nsresult result2
= DidMergeNotify(topTransaction
, tint
, didMerge
, result
);
876 if (NS_SUCCEEDED(result
))
879 if (NS_FAILED(result
)) {
880 // XXX: What do we do if this fails?
890 // Check to see if we've hit the max level of undo. If so,
891 // pop the bottom transaction off the undo stack and release it!
893 int32_t sz
= mUndoStack
.GetSize();
895 if (mMaxTransactionCount
> 0 && sz
>= mMaxTransactionCount
) {
896 nsRefPtr
<nsTransactionItem
> overflow
= mUndoStack
.PopBottom();
899 // Push the transaction on the undo stack: