Source code

Revision control

Other Tools

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