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/. */
5 
6 #include "mozilla/Assertions.h"
7 #include "mozilla/mozalloc.h"
8 #include "nsCOMPtr.h"
9 #include "nsDebug.h"
10 #include "nsError.h"
11 #include "nsISupportsBase.h"
12 #include "nsISupportsUtils.h"
13 #include "nsITransaction.h"
14 #include "nsITransactionList.h"
15 #include "nsITransactionListener.h"
16 #include "nsIWeakReference.h"
17 #include "nsTransactionItem.h"
18 #include "nsTransactionList.h"
19 #include "nsTransactionManager.h"
20 #include "nsTransactionStack.h"
21 
nsTransactionManager(int32_t aMaxTransactionCount)22 nsTransactionManager::nsTransactionManager(int32_t aMaxTransactionCount)
23   : mMaxTransactionCount(aMaxTransactionCount)
24   , mDoStack(nsTransactionStack::FOR_UNDO)
25   , mUndoStack(nsTransactionStack::FOR_UNDO)
26   , mRedoStack(nsTransactionStack::FOR_REDO)
27 {
28 }
29 
~nsTransactionManager()30 nsTransactionManager::~nsTransactionManager()
31 {
32 }
33 
34 NS_IMPL_CYCLE_COLLECTION_CLASS(nsTransactionManager)
35 
36 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsTransactionManager)
37   NS_IMPL_CYCLE_COLLECTION_UNLINK(mListeners)
38   tmp->mDoStack.DoUnlink();
39   tmp->mUndoStack.DoUnlink();
40   tmp->mRedoStack.DoUnlink();
41 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
42 
43 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsTransactionManager)
44   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListeners)
45   tmp->mDoStack.DoTraverse(cb);
46   tmp->mUndoStack.DoTraverse(cb);
47   tmp->mRedoStack.DoTraverse(cb);
48 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
49 
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTransactionManager)50 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTransactionManager)
51   NS_INTERFACE_MAP_ENTRY(nsITransactionManager)
52   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
53   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITransactionManager)
54 NS_INTERFACE_MAP_END
55 
56 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTransactionManager)
57 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTransactionManager)
58 
59 NS_IMETHODIMP
60 nsTransactionManager::DoTransaction(nsITransaction *aTransaction)
61 {
62   NS_ENSURE_TRUE(aTransaction, NS_ERROR_NULL_POINTER);
63 
64   bool doInterrupt = false;
65 
66   nsresult rv = WillDoNotify(aTransaction, &doInterrupt);
67   if (NS_FAILED(rv)) {
68     return rv;
69   }
70   if (doInterrupt) {
71     return NS_OK;
72   }
73 
74   rv = BeginTransaction(aTransaction, nullptr);
75   if (NS_FAILED(rv)) {
76     DidDoNotify(aTransaction, rv);
77     return rv;
78   }
79 
80   rv = EndTransaction(false);
81 
82   nsresult rv2 = DidDoNotify(aTransaction, rv);
83   if (NS_SUCCEEDED(rv)) {
84     rv = rv2;
85   }
86 
87   // XXX The result of EndTransaction() or DidDoNotify() if EndTransaction()
88   //     succeeded.
89   return rv;
90 }
91 
92 NS_IMETHODIMP
UndoTransaction()93 nsTransactionManager::UndoTransaction()
94 {
95   // It is illegal to call UndoTransaction() while the transaction manager is
96   // executing a  transaction's DoTransaction() method! If this happens,
97   // the UndoTransaction() request is ignored, and we return NS_ERROR_FAILURE.
98   if (!mDoStack.IsEmpty()) {
99     return NS_ERROR_FAILURE;
100   }
101 
102   // Peek at the top of the undo stack. Don't remove the transaction
103   // until it has successfully completed.
104   RefPtr<nsTransactionItem> tx = mUndoStack.Peek();
105   if (!tx) {
106     // Bail if there's nothing on the stack.
107     return NS_OK;
108   }
109 
110   nsCOMPtr<nsITransaction> t = tx->GetTransaction();
111   bool doInterrupt = false;
112   nsresult rv = WillUndoNotify(t, &doInterrupt);
113   if (NS_FAILED(rv)) {
114     return rv;
115   }
116   if (doInterrupt) {
117     return NS_OK;
118   }
119 
120   rv = tx->UndoTransaction(this);
121   if (NS_SUCCEEDED(rv)) {
122     tx = mUndoStack.Pop();
123     mRedoStack.Push(tx.forget());
124   }
125 
126   nsresult rv2 = DidUndoNotify(t, rv);
127   if (NS_SUCCEEDED(rv)) {
128     rv = rv2;
129   }
130 
131   // XXX The result of UndoTransaction() or DidUndoNotify() if UndoTransaction()
132   //     succeeded.
133   return rv;
134 }
135 
136 NS_IMETHODIMP
RedoTransaction()137 nsTransactionManager::RedoTransaction()
138 {
139   // It is illegal to call RedoTransaction() while the transaction manager is
140   // executing a  transaction's DoTransaction() method! If this happens,
141   // the RedoTransaction() request is ignored, and we return NS_ERROR_FAILURE.
142   if (!mDoStack.IsEmpty()) {
143     return NS_ERROR_FAILURE;
144   }
145 
146   // Peek at the top of the redo stack. Don't remove the transaction
147   // until it has successfully completed.
148   RefPtr<nsTransactionItem> tx = mRedoStack.Peek();
149   if (!tx) {
150     // Bail if there's nothing on the stack.
151     return NS_OK;
152   }
153 
154   nsCOMPtr<nsITransaction> t = tx->GetTransaction();
155   bool doInterrupt = false;
156   nsresult rv = WillRedoNotify(t, &doInterrupt);
157   if (NS_FAILED(rv)) {
158     return rv;
159   }
160   if (doInterrupt) {
161     return NS_OK;
162   }
163 
164   rv = tx->RedoTransaction(this);
165   if (NS_SUCCEEDED(rv)) {
166     tx = mRedoStack.Pop();
167     mUndoStack.Push(tx.forget());
168   }
169 
170   nsresult rv2 = DidRedoNotify(t, rv);
171   if (NS_SUCCEEDED(rv)) {
172     rv = rv2;
173   }
174 
175   // XXX The result of RedoTransaction() or DidRedoNotify() if RedoTransaction()
176   //     succeeded.
177   return rv;
178 }
179 
180 NS_IMETHODIMP
Clear()181 nsTransactionManager::Clear()
182 {
183   nsresult rv = ClearRedoStack();
184   if (NS_FAILED(rv)) {
185     return rv;
186   }
187   return ClearUndoStack();
188 }
189 
190 NS_IMETHODIMP
BeginBatch(nsISupports * aData)191 nsTransactionManager::BeginBatch(nsISupports* aData)
192 {
193   // We can batch independent transactions together by simply pushing
194   // a dummy transaction item on the do stack. This dummy transaction item
195   // will be popped off the do stack, and then pushed on the undo stack
196   // in EndBatch().
197   bool doInterrupt = false;
198   nsresult rv = WillBeginBatchNotify(&doInterrupt);
199   if (NS_FAILED(rv)) {
200     return rv;
201   }
202   if (doInterrupt) {
203     return NS_OK;
204   }
205 
206   rv = BeginTransaction(0, aData);
207 
208   nsresult rv2 = DidBeginBatchNotify(rv);
209   if (NS_SUCCEEDED(rv)) {
210     rv = rv2;
211   }
212 
213   // XXX The result of BeginTransaction() or DidBeginBatchNotify() if
214   //     BeginTransaction() succeeded.
215   return rv;
216 }
217 
218 NS_IMETHODIMP
EndBatch(bool aAllowEmpty)219 nsTransactionManager::EndBatch(bool aAllowEmpty)
220 {
221   // XXX: Need to add some mechanism to detect the case where the transaction
222   //      at the top of the do stack isn't the dummy transaction, so we can
223   //      throw an error!! This can happen if someone calls EndBatch() within
224   //      the DoTransaction() method of a transaction.
225   //
226   //      For now, we can detect this case by checking the value of the
227   //      dummy transaction's mTransaction field. If it is our dummy
228   //      transaction, it should be nullptr. This may not be true in the
229   //      future when we allow users to execute a transaction when beginning
230   //      a batch!!!!
231   RefPtr<nsTransactionItem> tx = mDoStack.Peek();
232   nsCOMPtr<nsITransaction> ti;
233   if (tx) {
234     ti = tx->GetTransaction();
235   }
236   if (!tx || ti) {
237     return NS_ERROR_FAILURE;
238   }
239 
240   bool doInterrupt = false;
241   nsresult rv = WillEndBatchNotify(&doInterrupt);
242   if (NS_FAILED(rv)) {
243     return rv;
244   }
245   if (doInterrupt) {
246     return NS_OK;
247   }
248 
249   rv = EndTransaction(aAllowEmpty);
250   nsresult rv2 = DidEndBatchNotify(rv);
251   if (NS_SUCCEEDED(rv)) {
252     rv = rv2;
253   }
254 
255   // XXX The result of EndTransaction() or DidEndBatchNotify() if
256   //     EndTransaction() succeeded.
257   return rv;
258 }
259 
260 NS_IMETHODIMP
GetNumberOfUndoItems(int32_t * aNumItems)261 nsTransactionManager::GetNumberOfUndoItems(int32_t *aNumItems)
262 {
263   *aNumItems = mUndoStack.GetSize();
264   return NS_OK;
265 }
266 
267 NS_IMETHODIMP
GetNumberOfRedoItems(int32_t * aNumItems)268 nsTransactionManager::GetNumberOfRedoItems(int32_t *aNumItems)
269 {
270   *aNumItems = mRedoStack.GetSize();
271   return NS_OK;
272 }
273 
274 NS_IMETHODIMP
GetMaxTransactionCount(int32_t * aMaxCount)275 nsTransactionManager::GetMaxTransactionCount(int32_t *aMaxCount)
276 {
277   NS_ENSURE_TRUE(aMaxCount, NS_ERROR_NULL_POINTER);
278   *aMaxCount = mMaxTransactionCount;
279   return NS_OK;
280 }
281 
282 NS_IMETHODIMP
SetMaxTransactionCount(int32_t aMaxCount)283 nsTransactionManager::SetMaxTransactionCount(int32_t aMaxCount)
284 {
285   // It is illegal to call SetMaxTransactionCount() while the transaction
286   // manager is executing a  transaction's DoTransaction() method because
287   // the undo and redo stacks might get pruned! If this happens, the
288   // SetMaxTransactionCount() request is ignored, and we return
289   // NS_ERROR_FAILURE.
290   if (!mDoStack.IsEmpty()) {
291     return NS_ERROR_FAILURE;
292   }
293 
294   // If aMaxCount is less than zero, the user wants unlimited
295   // levels of undo! No need to prune the undo or redo stacks!
296   if (aMaxCount < 0) {
297     mMaxTransactionCount = -1;
298     return NS_OK;
299   }
300 
301   // If aMaxCount is greater than the number of transactions that currently
302   // exist on the undo and redo stack, there is no need to prune the
303   // undo or redo stacks!
304   int32_t numUndoItems = mUndoStack.GetSize();
305   int32_t numRedoItems = mRedoStack.GetSize();
306   int32_t total = numUndoItems + numRedoItems;
307   if (aMaxCount > total) {
308     mMaxTransactionCount = aMaxCount;
309     return NS_OK;
310   }
311 
312   // Try getting rid of some transactions on the undo stack! Start at
313   // the bottom of the stack and pop towards the top.
314   while (numUndoItems > 0 && (numRedoItems + numUndoItems) > aMaxCount) {
315     RefPtr<nsTransactionItem> tx = mUndoStack.PopBottom();
316     if (!tx) {
317       return NS_ERROR_FAILURE;
318     }
319     --numUndoItems;
320   }
321 
322   // If necessary, get rid of some transactions on the redo stack! Start at
323   // the bottom of the stack and pop towards the top.
324   while (numRedoItems > 0 && (numRedoItems + numUndoItems) > aMaxCount) {
325     RefPtr<nsTransactionItem> tx = mRedoStack.PopBottom();
326     if (!tx) {
327       return NS_ERROR_FAILURE;
328     }
329     --numRedoItems;
330   }
331 
332   mMaxTransactionCount = aMaxCount;
333   return NS_OK;
334 }
335 
336 NS_IMETHODIMP
PeekUndoStack(nsITransaction ** aTransaction)337 nsTransactionManager::PeekUndoStack(nsITransaction **aTransaction)
338 {
339   MOZ_ASSERT(aTransaction);
340   *aTransaction = PeekUndoStack().take();
341   return NS_OK;
342 }
343 
344 already_AddRefed<nsITransaction>
PeekUndoStack()345 nsTransactionManager::PeekUndoStack()
346 {
347   RefPtr<nsTransactionItem> tx = mUndoStack.Peek();
348   if (!tx) {
349     return nullptr;
350   }
351   return tx->GetTransaction();
352 }
353 
354 NS_IMETHODIMP
PeekRedoStack(nsITransaction ** aTransaction)355 nsTransactionManager::PeekRedoStack(nsITransaction** aTransaction)
356 {
357   MOZ_ASSERT(aTransaction);
358   *aTransaction = PeekRedoStack().take();
359   return NS_OK;
360 }
361 
362 already_AddRefed<nsITransaction>
PeekRedoStack()363 nsTransactionManager::PeekRedoStack()
364 {
365   RefPtr<nsTransactionItem> tx = mRedoStack.Peek();
366   if (!tx) {
367     return nullptr;
368   }
369   return tx->GetTransaction();
370 }
371 
372 NS_IMETHODIMP
GetUndoList(nsITransactionList ** aTransactionList)373 nsTransactionManager::GetUndoList(nsITransactionList **aTransactionList)
374 {
375   NS_ENSURE_TRUE(aTransactionList, NS_ERROR_NULL_POINTER);
376 
377   *aTransactionList = (nsITransactionList *)new nsTransactionList(this, &mUndoStack);
378   NS_IF_ADDREF(*aTransactionList);
379   return (! *aTransactionList) ? NS_ERROR_OUT_OF_MEMORY : NS_OK;
380 }
381 
382 NS_IMETHODIMP
GetRedoList(nsITransactionList ** aTransactionList)383 nsTransactionManager::GetRedoList(nsITransactionList **aTransactionList)
384 {
385   NS_ENSURE_TRUE(aTransactionList, NS_ERROR_NULL_POINTER);
386 
387   *aTransactionList = (nsITransactionList *)new nsTransactionList(this, &mRedoStack);
388   NS_IF_ADDREF(*aTransactionList);
389   return (! *aTransactionList) ? NS_ERROR_OUT_OF_MEMORY : NS_OK;
390 }
391 
392 nsresult
BatchTopUndo()393 nsTransactionManager::BatchTopUndo()
394 {
395   if (mUndoStack.GetSize() < 2) {
396     // Not enough transactions to merge into one batch.
397     return NS_OK;
398   }
399 
400   RefPtr<nsTransactionItem> lastUndo;
401   RefPtr<nsTransactionItem> previousUndo;
402 
403   lastUndo = mUndoStack.Pop();
404   MOZ_ASSERT(lastUndo, "There should be at least two transactions.");
405 
406   previousUndo = mUndoStack.Peek();
407   MOZ_ASSERT(previousUndo, "There should be at least two transactions.");
408 
409   nsresult rv = previousUndo->AddChild(lastUndo);
410 
411   // Transfer data from the transactions that is going to be
412   // merged to the transaction that it is being merged with.
413   nsCOMArray<nsISupports>& lastData = lastUndo->GetData();
414   nsCOMArray<nsISupports>& previousData = previousUndo->GetData();
415   NS_ENSURE_TRUE(previousData.AppendObjects(lastData), NS_ERROR_UNEXPECTED);
416   lastData.Clear();
417   return rv;
418 }
419 
420 nsresult
RemoveTopUndo()421 nsTransactionManager::RemoveTopUndo()
422 {
423   if (mUndoStack.IsEmpty()) {
424     return NS_OK;
425   }
426 
427   RefPtr<nsTransactionItem> lastUndo = mUndoStack.Pop();
428   return NS_OK;
429 }
430 
431 NS_IMETHODIMP
AddListener(nsITransactionListener * aListener)432 nsTransactionManager::AddListener(nsITransactionListener *aListener)
433 {
434   NS_ENSURE_TRUE(aListener, NS_ERROR_NULL_POINTER);
435   return mListeners.AppendObject(aListener) ? NS_OK : NS_ERROR_FAILURE;
436 }
437 
438 NS_IMETHODIMP
RemoveListener(nsITransactionListener * aListener)439 nsTransactionManager::RemoveListener(nsITransactionListener *aListener)
440 {
441   NS_ENSURE_TRUE(aListener, NS_ERROR_NULL_POINTER);
442   return mListeners.RemoveObject(aListener) ? NS_OK : NS_ERROR_FAILURE;
443 }
444 
445 NS_IMETHODIMP
ClearUndoStack()446 nsTransactionManager::ClearUndoStack()
447 {
448   mUndoStack.Clear();
449   return NS_OK;
450 }
451 
452 NS_IMETHODIMP
ClearRedoStack()453 nsTransactionManager::ClearRedoStack()
454 {
455   mRedoStack.Clear();
456   return NS_OK;
457 }
458 
459 nsresult
WillDoNotify(nsITransaction * aTransaction,bool * aInterrupt)460 nsTransactionManager::WillDoNotify(nsITransaction *aTransaction, bool *aInterrupt)
461 {
462   for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) {
463     nsITransactionListener* listener = mListeners[i];
464     NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
465 
466     nsresult rv = listener->WillDo(this, aTransaction, aInterrupt);
467     if (NS_FAILED(rv) || *aInterrupt) {
468       return rv;
469     }
470   }
471   return NS_OK;
472 }
473 
474 nsresult
DidDoNotify(nsITransaction * aTransaction,nsresult aDoResult)475 nsTransactionManager::DidDoNotify(nsITransaction *aTransaction, nsresult aDoResult)
476 {
477   for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) {
478     nsITransactionListener* listener = mListeners[i];
479     NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
480 
481     nsresult rv = listener->DidDo(this, aTransaction, aDoResult);
482     if (NS_FAILED(rv)) {
483       return rv;
484     }
485   }
486   return NS_OK;
487 }
488 
489 nsresult
WillUndoNotify(nsITransaction * aTransaction,bool * aInterrupt)490 nsTransactionManager::WillUndoNotify(nsITransaction *aTransaction, bool *aInterrupt)
491 {
492   for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) {
493     nsITransactionListener* listener = mListeners[i];
494     NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
495 
496     nsresult rv = listener->WillUndo(this, aTransaction, aInterrupt);
497     if (NS_FAILED(rv) || *aInterrupt) {
498       return rv;
499     }
500   }
501   return NS_OK;
502 }
503 
504 nsresult
DidUndoNotify(nsITransaction * aTransaction,nsresult aUndoResult)505 nsTransactionManager::DidUndoNotify(nsITransaction *aTransaction, nsresult aUndoResult)
506 {
507   for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) {
508     nsITransactionListener* listener = mListeners[i];
509     NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
510 
511     nsresult rv = listener->DidUndo(this, aTransaction, aUndoResult);
512     if (NS_FAILED(rv)) {
513       return rv;
514     }
515   }
516   return NS_OK;
517 }
518 
519 nsresult
WillRedoNotify(nsITransaction * aTransaction,bool * aInterrupt)520 nsTransactionManager::WillRedoNotify(nsITransaction *aTransaction, bool *aInterrupt)
521 {
522   for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) {
523     nsITransactionListener* listener = mListeners[i];
524     NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
525 
526     nsresult rv = listener->WillRedo(this, aTransaction, aInterrupt);
527     if (NS_FAILED(rv) || *aInterrupt) {
528       return rv;
529     }
530   }
531   return NS_OK;
532 }
533 
534 nsresult
DidRedoNotify(nsITransaction * aTransaction,nsresult aRedoResult)535 nsTransactionManager::DidRedoNotify(nsITransaction *aTransaction, nsresult aRedoResult)
536 {
537   for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) {
538     nsITransactionListener* listener = mListeners[i];
539     NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
540 
541     nsresult rv = listener->DidRedo(this, aTransaction, aRedoResult);
542     if (NS_FAILED(rv)) {
543       return rv;
544     }
545   }
546   return NS_OK;
547 }
548 
549 nsresult
WillBeginBatchNotify(bool * aInterrupt)550 nsTransactionManager::WillBeginBatchNotify(bool *aInterrupt)
551 {
552   for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) {
553     nsITransactionListener* listener = mListeners[i];
554     NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
555 
556     nsresult rv = listener->WillBeginBatch(this, aInterrupt);
557     if (NS_FAILED(rv) || *aInterrupt) {
558       return rv;
559     }
560   }
561   return NS_OK;
562 }
563 
564 nsresult
DidBeginBatchNotify(nsresult aResult)565 nsTransactionManager::DidBeginBatchNotify(nsresult aResult)
566 {
567   for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) {
568     nsITransactionListener* listener = mListeners[i];
569     NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
570 
571     nsresult rv = listener->DidBeginBatch(this, aResult);
572     if (NS_FAILED(rv)) {
573       return rv;
574     }
575   }
576   return NS_OK;
577 }
578 
579 nsresult
WillEndBatchNotify(bool * aInterrupt)580 nsTransactionManager::WillEndBatchNotify(bool *aInterrupt)
581 {
582   for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) {
583     nsITransactionListener* listener = mListeners[i];
584     NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
585 
586     nsresult rv = listener->WillEndBatch(this, aInterrupt);
587     if (NS_FAILED(rv) || *aInterrupt) {
588       return rv;
589     }
590   }
591   return NS_OK;
592 }
593 
594 nsresult
DidEndBatchNotify(nsresult aResult)595 nsTransactionManager::DidEndBatchNotify(nsresult aResult)
596 {
597   for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) {
598     nsITransactionListener* listener = mListeners[i];
599     NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
600 
601     nsresult rv = listener->DidEndBatch(this, aResult);
602     if (NS_FAILED(rv)) {
603       return rv;
604     }
605   }
606   return NS_OK;
607 }
608 
609 nsresult
WillMergeNotify(nsITransaction * aTop,nsITransaction * aTransaction,bool * aInterrupt)610 nsTransactionManager::WillMergeNotify(nsITransaction *aTop, nsITransaction *aTransaction, bool *aInterrupt)
611 {
612   for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) {
613     nsITransactionListener* listener = mListeners[i];
614     NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
615 
616     nsresult rv = listener->WillMerge(this, aTop, aTransaction, aInterrupt);
617     if (NS_FAILED(rv) || *aInterrupt) {
618       return rv;
619     }
620   }
621   return NS_OK;
622 }
623 
624 nsresult
DidMergeNotify(nsITransaction * aTop,nsITransaction * aTransaction,bool aDidMerge,nsresult aMergeResult)625 nsTransactionManager::DidMergeNotify(nsITransaction *aTop,
626                                      nsITransaction *aTransaction,
627                                      bool aDidMerge,
628                                      nsresult aMergeResult)
629 {
630   for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) {
631     nsITransactionListener* listener = mListeners[i];
632     NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
633 
634     nsresult rv =
635       listener->DidMerge(this, aTop, aTransaction, aDidMerge, aMergeResult);
636     if (NS_FAILED(rv)) {
637       return rv;
638     }
639   }
640   return NS_OK;
641 }
642 
643 nsresult
BeginTransaction(nsITransaction * aTransaction,nsISupports * aData)644 nsTransactionManager::BeginTransaction(nsITransaction *aTransaction,
645                                        nsISupports *aData)
646 {
647   // XXX: POSSIBLE OPTIMIZATION
648   //      We could use a factory that pre-allocates/recycles transaction items.
649   RefPtr<nsTransactionItem> tx = new nsTransactionItem(aTransaction);
650   if (!tx) {
651     return NS_ERROR_OUT_OF_MEMORY;
652   }
653 
654   if (aData) {
655     nsCOMArray<nsISupports>& data = tx->GetData();
656     data.AppendObject(aData);
657   }
658 
659   mDoStack.Push(tx);
660 
661   nsresult rv = tx->DoTransaction();
662   if (NS_FAILED(rv)) {
663     tx = mDoStack.Pop();
664     return rv;
665   }
666   return NS_OK;
667 }
668 
669 nsresult
EndTransaction(bool aAllowEmpty)670 nsTransactionManager::EndTransaction(bool aAllowEmpty)
671 {
672   RefPtr<nsTransactionItem> tx = mDoStack.Pop();
673   if (!tx) {
674     return NS_ERROR_FAILURE;
675   }
676 
677   nsCOMPtr<nsITransaction> tint = tx->GetTransaction();
678   if (!tint && !aAllowEmpty) {
679     // If we get here, the transaction must be a dummy batch transaction
680     // created by BeginBatch(). If it contains no children, get rid of it!
681     int32_t nc = 0;
682     tx->GetNumberOfChildren(&nc);
683     if (!nc) {
684       return NS_OK;
685     }
686   }
687 
688   // Check if the transaction is transient. If it is, there's nothing
689   // more to do, just return.
690   bool isTransient = false;
691   nsresult rv = NS_OK;
692   if (tint) {
693     rv = tint->GetIsTransient(&isTransient);
694   }
695   if (NS_FAILED(rv) || isTransient || !mMaxTransactionCount) {
696     // XXX: Should we be clearing the redo stack if the transaction
697     //      is transient and there is nothing on the do stack?
698     return rv;
699   }
700 
701   // Check if there is a transaction on the do stack. If there is,
702   // the current transaction is a "sub" transaction, and should
703   // be added to the transaction at the top of the do stack.
704   RefPtr<nsTransactionItem> top = mDoStack.Peek();
705   if (top) {
706     return top->AddChild(tx); // XXX: What do we do if this fails?
707   }
708 
709   // The transaction succeeded, so clear the redo stack.
710   rv = ClearRedoStack();
711   if (NS_FAILED(rv)) {
712     // XXX: What do we do if this fails?
713   }
714 
715   // Check if we can coalesce this transaction with the one at the top
716   // of the undo stack.
717   top = mUndoStack.Peek();
718   if (tint && top) {
719     bool didMerge = false;
720     nsCOMPtr<nsITransaction> topTransaction = top->GetTransaction();
721     if (topTransaction) {
722       bool doInterrupt = false;
723       rv = WillMergeNotify(topTransaction, tint, &doInterrupt);
724       NS_ENSURE_SUCCESS(rv, rv);
725 
726       if (!doInterrupt) {
727         rv = topTransaction->Merge(tint, &didMerge);
728         nsresult rv2 = DidMergeNotify(topTransaction, tint, didMerge, rv);
729         if (NS_SUCCEEDED(rv)) {
730           rv = rv2;
731         }
732         if (NS_FAILED(rv)) {
733           // XXX: What do we do if this fails?
734         }
735         if (didMerge) {
736           return rv;
737         }
738       }
739     }
740   }
741 
742   // Check to see if we've hit the max level of undo. If so,
743   // pop the bottom transaction off the undo stack and release it!
744   int32_t sz = mUndoStack.GetSize();
745   if (mMaxTransactionCount > 0 && sz >= mMaxTransactionCount) {
746     RefPtr<nsTransactionItem> overflow = mUndoStack.PopBottom();
747   }
748 
749   // Push the transaction on the undo stack:
750   mUndoStack.Push(tx.forget());
751   return NS_OK;
752 }
753