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