Coverage Report

Created: 2018-09-25 14:53

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