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