Line data Source code
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 :
22 2 : nsTransactionManager::nsTransactionManager(int32_t aMaxTransactionCount)
23 : : mMaxTransactionCount(aMaxTransactionCount)
24 : , mDoStack(nsTransactionStack::FOR_UNDO)
25 : , mUndoStack(nsTransactionStack::FOR_UNDO)
26 2 : , mRedoStack(nsTransactionStack::FOR_REDO)
27 : {
28 2 : }
29 :
30 2 : nsTransactionManager::~nsTransactionManager()
31 : {
32 3 : }
33 :
34 : NS_IMPL_CYCLE_COLLECTION_CLASS(nsTransactionManager)
35 :
36 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsTransactionManager)
37 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mListeners)
38 0 : tmp->mDoStack.DoUnlink();
39 0 : tmp->mUndoStack.DoUnlink();
40 0 : tmp->mRedoStack.DoUnlink();
41 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
42 :
43 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsTransactionManager)
44 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListeners)
45 0 : tmp->mDoStack.DoTraverse(cb);
46 0 : tmp->mUndoStack.DoTraverse(cb);
47 0 : tmp->mRedoStack.DoTraverse(cb);
48 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
49 :
50 10 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTransactionManager)
51 4 : NS_INTERFACE_MAP_ENTRY(nsITransactionManager)
52 0 : NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
53 0 : NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITransactionManager)
54 0 : NS_INTERFACE_MAP_END
55 :
56 11 : NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTransactionManager)
57 11 : NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTransactionManager)
58 :
59 : NS_IMETHODIMP
60 3 : nsTransactionManager::DoTransaction(nsITransaction *aTransaction)
61 : {
62 3 : NS_ENSURE_TRUE(aTransaction, NS_ERROR_NULL_POINTER);
63 :
64 3 : bool doInterrupt = false;
65 :
66 3 : nsresult rv = WillDoNotify(aTransaction, &doInterrupt);
67 3 : if (NS_FAILED(rv)) {
68 0 : return rv;
69 : }
70 3 : if (doInterrupt) {
71 0 : return NS_OK;
72 : }
73 :
74 3 : rv = BeginTransaction(aTransaction, nullptr);
75 3 : if (NS_FAILED(rv)) {
76 0 : DidDoNotify(aTransaction, rv);
77 0 : return rv;
78 : }
79 :
80 3 : rv = EndTransaction(false);
81 :
82 3 : nsresult rv2 = DidDoNotify(aTransaction, rv);
83 3 : if (NS_SUCCEEDED(rv)) {
84 3 : rv = rv2;
85 : }
86 :
87 : // XXX The result of EndTransaction() or DidDoNotify() if EndTransaction()
88 : // succeeded.
89 3 : return rv;
90 : }
91 :
92 : NS_IMETHODIMP
93 0 : 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 0 : if (!mDoStack.IsEmpty()) {
99 0 : 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 0 : RefPtr<nsTransactionItem> tx = mUndoStack.Peek();
105 0 : if (!tx) {
106 : // Bail if there's nothing on the stack.
107 0 : return NS_OK;
108 : }
109 :
110 0 : nsCOMPtr<nsITransaction> t = tx->GetTransaction();
111 0 : bool doInterrupt = false;
112 0 : nsresult rv = WillUndoNotify(t, &doInterrupt);
113 0 : if (NS_FAILED(rv)) {
114 0 : return rv;
115 : }
116 0 : if (doInterrupt) {
117 0 : return NS_OK;
118 : }
119 :
120 0 : rv = tx->UndoTransaction(this);
121 0 : if (NS_SUCCEEDED(rv)) {
122 0 : tx = mUndoStack.Pop();
123 0 : mRedoStack.Push(tx.forget());
124 : }
125 :
126 0 : nsresult rv2 = DidUndoNotify(t, rv);
127 0 : if (NS_SUCCEEDED(rv)) {
128 0 : rv = rv2;
129 : }
130 :
131 : // XXX The result of UndoTransaction() or DidUndoNotify() if UndoTransaction()
132 : // succeeded.
133 0 : return rv;
134 : }
135 :
136 : NS_IMETHODIMP
137 0 : 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 0 : if (!mDoStack.IsEmpty()) {
143 0 : 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 0 : RefPtr<nsTransactionItem> tx = mRedoStack.Peek();
149 0 : if (!tx) {
150 : // Bail if there's nothing on the stack.
151 0 : return NS_OK;
152 : }
153 :
154 0 : nsCOMPtr<nsITransaction> t = tx->GetTransaction();
155 0 : bool doInterrupt = false;
156 0 : nsresult rv = WillRedoNotify(t, &doInterrupt);
157 0 : if (NS_FAILED(rv)) {
158 0 : return rv;
159 : }
160 0 : if (doInterrupt) {
161 0 : return NS_OK;
162 : }
163 :
164 0 : rv = tx->RedoTransaction(this);
165 0 : if (NS_SUCCEEDED(rv)) {
166 0 : tx = mRedoStack.Pop();
167 0 : mUndoStack.Push(tx.forget());
168 : }
169 :
170 0 : nsresult rv2 = DidRedoNotify(t, rv);
171 0 : if (NS_SUCCEEDED(rv)) {
172 0 : rv = rv2;
173 : }
174 :
175 : // XXX The result of RedoTransaction() or DidRedoNotify() if RedoTransaction()
176 : // succeeded.
177 0 : return rv;
178 : }
179 :
180 : NS_IMETHODIMP
181 2 : nsTransactionManager::Clear()
182 : {
183 2 : nsresult rv = ClearRedoStack();
184 2 : if (NS_FAILED(rv)) {
185 0 : return rv;
186 : }
187 2 : return ClearUndoStack();
188 : }
189 :
190 : NS_IMETHODIMP
191 0 : 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 0 : bool doInterrupt = false;
198 0 : nsresult rv = WillBeginBatchNotify(&doInterrupt);
199 0 : if (NS_FAILED(rv)) {
200 0 : return rv;
201 : }
202 0 : if (doInterrupt) {
203 0 : return NS_OK;
204 : }
205 :
206 0 : rv = BeginTransaction(0, aData);
207 :
208 0 : nsresult rv2 = DidBeginBatchNotify(rv);
209 0 : if (NS_SUCCEEDED(rv)) {
210 0 : rv = rv2;
211 : }
212 :
213 : // XXX The result of BeginTransaction() or DidBeginBatchNotify() if
214 : // BeginTransaction() succeeded.
215 0 : return rv;
216 : }
217 :
218 : NS_IMETHODIMP
219 0 : 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 0 : RefPtr<nsTransactionItem> tx = mDoStack.Peek();
232 0 : nsCOMPtr<nsITransaction> ti;
233 0 : if (tx) {
234 0 : ti = tx->GetTransaction();
235 : }
236 0 : if (!tx || ti) {
237 0 : return NS_ERROR_FAILURE;
238 : }
239 :
240 0 : bool doInterrupt = false;
241 0 : nsresult rv = WillEndBatchNotify(&doInterrupt);
242 0 : if (NS_FAILED(rv)) {
243 0 : return rv;
244 : }
245 0 : if (doInterrupt) {
246 0 : return NS_OK;
247 : }
248 :
249 0 : rv = EndTransaction(aAllowEmpty);
250 0 : nsresult rv2 = DidEndBatchNotify(rv);
251 0 : if (NS_SUCCEEDED(rv)) {
252 0 : rv = rv2;
253 : }
254 :
255 : // XXX The result of EndTransaction() or DidEndBatchNotify() if
256 : // EndTransaction() succeeded.
257 0 : return rv;
258 : }
259 :
260 : NS_IMETHODIMP
261 2 : nsTransactionManager::GetNumberOfUndoItems(int32_t *aNumItems)
262 : {
263 2 : *aNumItems = mUndoStack.GetSize();
264 2 : return NS_OK;
265 : }
266 :
267 : NS_IMETHODIMP
268 1 : nsTransactionManager::GetNumberOfRedoItems(int32_t *aNumItems)
269 : {
270 1 : *aNumItems = mRedoStack.GetSize();
271 1 : return NS_OK;
272 : }
273 :
274 : NS_IMETHODIMP
275 0 : nsTransactionManager::GetMaxTransactionCount(int32_t *aMaxCount)
276 : {
277 0 : NS_ENSURE_TRUE(aMaxCount, NS_ERROR_NULL_POINTER);
278 0 : *aMaxCount = mMaxTransactionCount;
279 0 : return NS_OK;
280 : }
281 :
282 : NS_IMETHODIMP
283 6 : 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 6 : if (!mDoStack.IsEmpty()) {
291 0 : 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 6 : if (aMaxCount < 0) {
297 3 : mMaxTransactionCount = -1;
298 3 : 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 3 : int32_t numUndoItems = mUndoStack.GetSize();
305 3 : int32_t numRedoItems = mRedoStack.GetSize();
306 3 : int32_t total = numUndoItems + numRedoItems;
307 3 : if (aMaxCount > total) {
308 2 : mMaxTransactionCount = aMaxCount;
309 2 : 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 1 : while (numUndoItems > 0 && (numRedoItems + numUndoItems) > aMaxCount) {
315 0 : RefPtr<nsTransactionItem> tx = mUndoStack.PopBottom();
316 0 : if (!tx) {
317 0 : return NS_ERROR_FAILURE;
318 : }
319 0 : --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 1 : while (numRedoItems > 0 && (numRedoItems + numUndoItems) > aMaxCount) {
325 0 : RefPtr<nsTransactionItem> tx = mRedoStack.PopBottom();
326 0 : if (!tx) {
327 0 : return NS_ERROR_FAILURE;
328 : }
329 0 : --numRedoItems;
330 : }
331 :
332 1 : mMaxTransactionCount = aMaxCount;
333 1 : return NS_OK;
334 : }
335 :
336 : NS_IMETHODIMP
337 0 : nsTransactionManager::PeekUndoStack(nsITransaction **aTransaction)
338 : {
339 0 : MOZ_ASSERT(aTransaction);
340 0 : *aTransaction = PeekUndoStack().take();
341 0 : return NS_OK;
342 : }
343 :
344 : already_AddRefed<nsITransaction>
345 1 : nsTransactionManager::PeekUndoStack()
346 : {
347 2 : RefPtr<nsTransactionItem> tx = mUndoStack.Peek();
348 1 : if (!tx) {
349 1 : return nullptr;
350 : }
351 0 : return tx->GetTransaction();
352 : }
353 :
354 : NS_IMETHODIMP
355 0 : nsTransactionManager::PeekRedoStack(nsITransaction** aTransaction)
356 : {
357 0 : MOZ_ASSERT(aTransaction);
358 0 : *aTransaction = PeekRedoStack().take();
359 0 : return NS_OK;
360 : }
361 :
362 : already_AddRefed<nsITransaction>
363 0 : nsTransactionManager::PeekRedoStack()
364 : {
365 0 : RefPtr<nsTransactionItem> tx = mRedoStack.Peek();
366 0 : if (!tx) {
367 0 : return nullptr;
368 : }
369 0 : return tx->GetTransaction();
370 : }
371 :
372 : NS_IMETHODIMP
373 0 : nsTransactionManager::GetUndoList(nsITransactionList **aTransactionList)
374 : {
375 0 : NS_ENSURE_TRUE(aTransactionList, NS_ERROR_NULL_POINTER);
376 :
377 0 : *aTransactionList = (nsITransactionList *)new nsTransactionList(this, &mUndoStack);
378 0 : NS_IF_ADDREF(*aTransactionList);
379 0 : return (! *aTransactionList) ? NS_ERROR_OUT_OF_MEMORY : NS_OK;
380 : }
381 :
382 : NS_IMETHODIMP
383 0 : nsTransactionManager::GetRedoList(nsITransactionList **aTransactionList)
384 : {
385 0 : NS_ENSURE_TRUE(aTransactionList, NS_ERROR_NULL_POINTER);
386 :
387 0 : *aTransactionList = (nsITransactionList *)new nsTransactionList(this, &mRedoStack);
388 0 : NS_IF_ADDREF(*aTransactionList);
389 0 : return (! *aTransactionList) ? NS_ERROR_OUT_OF_MEMORY : NS_OK;
390 : }
391 :
392 : nsresult
393 0 : nsTransactionManager::BatchTopUndo()
394 : {
395 0 : if (mUndoStack.GetSize() < 2) {
396 : // Not enough transactions to merge into one batch.
397 0 : return NS_OK;
398 : }
399 :
400 0 : RefPtr<nsTransactionItem> lastUndo;
401 0 : RefPtr<nsTransactionItem> previousUndo;
402 :
403 0 : lastUndo = mUndoStack.Pop();
404 0 : MOZ_ASSERT(lastUndo, "There should be at least two transactions.");
405 :
406 0 : previousUndo = mUndoStack.Peek();
407 0 : MOZ_ASSERT(previousUndo, "There should be at least two transactions.");
408 :
409 0 : 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 0 : nsCOMArray<nsISupports>& lastData = lastUndo->GetData();
414 0 : nsCOMArray<nsISupports>& previousData = previousUndo->GetData();
415 0 : NS_ENSURE_TRUE(previousData.AppendObjects(lastData), NS_ERROR_UNEXPECTED);
416 0 : lastData.Clear();
417 0 : return rv;
418 : }
419 :
420 : nsresult
421 0 : nsTransactionManager::RemoveTopUndo()
422 : {
423 0 : if (mUndoStack.IsEmpty()) {
424 0 : return NS_OK;
425 : }
426 :
427 0 : RefPtr<nsTransactionItem> lastUndo = mUndoStack.Pop();
428 0 : return NS_OK;
429 : }
430 :
431 : NS_IMETHODIMP
432 0 : nsTransactionManager::AddListener(nsITransactionListener *aListener)
433 : {
434 0 : NS_ENSURE_TRUE(aListener, NS_ERROR_NULL_POINTER);
435 0 : return mListeners.AppendObject(aListener) ? NS_OK : NS_ERROR_FAILURE;
436 : }
437 :
438 : NS_IMETHODIMP
439 0 : nsTransactionManager::RemoveListener(nsITransactionListener *aListener)
440 : {
441 0 : NS_ENSURE_TRUE(aListener, NS_ERROR_NULL_POINTER);
442 0 : return mListeners.RemoveObject(aListener) ? NS_OK : NS_ERROR_FAILURE;
443 : }
444 :
445 : NS_IMETHODIMP
446 2 : nsTransactionManager::ClearUndoStack()
447 : {
448 2 : mUndoStack.Clear();
449 2 : return NS_OK;
450 : }
451 :
452 : NS_IMETHODIMP
453 2 : nsTransactionManager::ClearRedoStack()
454 : {
455 2 : mRedoStack.Clear();
456 2 : return NS_OK;
457 : }
458 :
459 : nsresult
460 3 : nsTransactionManager::WillDoNotify(nsITransaction *aTransaction, bool *aInterrupt)
461 : {
462 3 : for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) {
463 0 : nsITransactionListener* listener = mListeners[i];
464 0 : NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
465 :
466 0 : nsresult rv = listener->WillDo(this, aTransaction, aInterrupt);
467 0 : if (NS_FAILED(rv) || *aInterrupt) {
468 0 : return rv;
469 : }
470 : }
471 3 : return NS_OK;
472 : }
473 :
474 : nsresult
475 3 : nsTransactionManager::DidDoNotify(nsITransaction *aTransaction, nsresult aDoResult)
476 : {
477 3 : for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) {
478 0 : nsITransactionListener* listener = mListeners[i];
479 0 : NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
480 :
481 0 : nsresult rv = listener->DidDo(this, aTransaction, aDoResult);
482 0 : if (NS_FAILED(rv)) {
483 0 : return rv;
484 : }
485 : }
486 3 : return NS_OK;
487 : }
488 :
489 : nsresult
490 0 : nsTransactionManager::WillUndoNotify(nsITransaction *aTransaction, bool *aInterrupt)
491 : {
492 0 : for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) {
493 0 : nsITransactionListener* listener = mListeners[i];
494 0 : NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
495 :
496 0 : nsresult rv = listener->WillUndo(this, aTransaction, aInterrupt);
497 0 : if (NS_FAILED(rv) || *aInterrupt) {
498 0 : return rv;
499 : }
500 : }
501 0 : return NS_OK;
502 : }
503 :
504 : nsresult
505 0 : nsTransactionManager::DidUndoNotify(nsITransaction *aTransaction, nsresult aUndoResult)
506 : {
507 0 : for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) {
508 0 : nsITransactionListener* listener = mListeners[i];
509 0 : NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
510 :
511 0 : nsresult rv = listener->DidUndo(this, aTransaction, aUndoResult);
512 0 : if (NS_FAILED(rv)) {
513 0 : return rv;
514 : }
515 : }
516 0 : return NS_OK;
517 : }
518 :
519 : nsresult
520 0 : nsTransactionManager::WillRedoNotify(nsITransaction *aTransaction, bool *aInterrupt)
521 : {
522 0 : for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) {
523 0 : nsITransactionListener* listener = mListeners[i];
524 0 : NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
525 :
526 0 : nsresult rv = listener->WillRedo(this, aTransaction, aInterrupt);
527 0 : if (NS_FAILED(rv) || *aInterrupt) {
528 0 : return rv;
529 : }
530 : }
531 0 : return NS_OK;
532 : }
533 :
534 : nsresult
535 0 : nsTransactionManager::DidRedoNotify(nsITransaction *aTransaction, nsresult aRedoResult)
536 : {
537 0 : for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) {
538 0 : nsITransactionListener* listener = mListeners[i];
539 0 : NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
540 :
541 0 : nsresult rv = listener->DidRedo(this, aTransaction, aRedoResult);
542 0 : if (NS_FAILED(rv)) {
543 0 : return rv;
544 : }
545 : }
546 0 : return NS_OK;
547 : }
548 :
549 : nsresult
550 0 : nsTransactionManager::WillBeginBatchNotify(bool *aInterrupt)
551 : {
552 0 : for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) {
553 0 : nsITransactionListener* listener = mListeners[i];
554 0 : NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
555 :
556 0 : nsresult rv = listener->WillBeginBatch(this, aInterrupt);
557 0 : if (NS_FAILED(rv) || *aInterrupt) {
558 0 : return rv;
559 : }
560 : }
561 0 : return NS_OK;
562 : }
563 :
564 : nsresult
565 0 : nsTransactionManager::DidBeginBatchNotify(nsresult aResult)
566 : {
567 0 : for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) {
568 0 : nsITransactionListener* listener = mListeners[i];
569 0 : NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
570 :
571 0 : nsresult rv = listener->DidBeginBatch(this, aResult);
572 0 : if (NS_FAILED(rv)) {
573 0 : return rv;
574 : }
575 : }
576 0 : return NS_OK;
577 : }
578 :
579 : nsresult
580 0 : nsTransactionManager::WillEndBatchNotify(bool *aInterrupt)
581 : {
582 0 : for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) {
583 0 : nsITransactionListener* listener = mListeners[i];
584 0 : NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
585 :
586 0 : nsresult rv = listener->WillEndBatch(this, aInterrupt);
587 0 : if (NS_FAILED(rv) || *aInterrupt) {
588 0 : return rv;
589 : }
590 : }
591 0 : return NS_OK;
592 : }
593 :
594 : nsresult
595 0 : nsTransactionManager::DidEndBatchNotify(nsresult aResult)
596 : {
597 0 : for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) {
598 0 : nsITransactionListener* listener = mListeners[i];
599 0 : NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
600 :
601 0 : nsresult rv = listener->DidEndBatch(this, aResult);
602 0 : if (NS_FAILED(rv)) {
603 0 : return rv;
604 : }
605 : }
606 0 : return NS_OK;
607 : }
608 :
609 : nsresult
610 0 : nsTransactionManager::WillMergeNotify(nsITransaction *aTop, nsITransaction *aTransaction, bool *aInterrupt)
611 : {
612 0 : for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) {
613 0 : nsITransactionListener* listener = mListeners[i];
614 0 : NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
615 :
616 0 : nsresult rv = listener->WillMerge(this, aTop, aTransaction, aInterrupt);
617 0 : if (NS_FAILED(rv) || *aInterrupt) {
618 0 : return rv;
619 : }
620 : }
621 0 : return NS_OK;
622 : }
623 :
624 : nsresult
625 0 : nsTransactionManager::DidMergeNotify(nsITransaction *aTop,
626 : nsITransaction *aTransaction,
627 : bool aDidMerge,
628 : nsresult aMergeResult)
629 : {
630 0 : for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) {
631 0 : nsITransactionListener* listener = mListeners[i];
632 0 : NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE);
633 :
634 : nsresult rv =
635 0 : listener->DidMerge(this, aTop, aTransaction, aDidMerge, aMergeResult);
636 0 : if (NS_FAILED(rv)) {
637 0 : return rv;
638 : }
639 : }
640 0 : return NS_OK;
641 : }
642 :
643 : nsresult
644 3 : 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 6 : RefPtr<nsTransactionItem> tx = new nsTransactionItem(aTransaction);
650 3 : if (!tx) {
651 0 : return NS_ERROR_OUT_OF_MEMORY;
652 : }
653 :
654 3 : if (aData) {
655 0 : nsCOMArray<nsISupports>& data = tx->GetData();
656 0 : data.AppendObject(aData);
657 : }
658 :
659 3 : mDoStack.Push(tx);
660 :
661 3 : nsresult rv = tx->DoTransaction();
662 3 : if (NS_FAILED(rv)) {
663 0 : tx = mDoStack.Pop();
664 0 : return rv;
665 : }
666 3 : return NS_OK;
667 : }
668 :
669 : nsresult
670 3 : nsTransactionManager::EndTransaction(bool aAllowEmpty)
671 : {
672 6 : RefPtr<nsTransactionItem> tx = mDoStack.Pop();
673 3 : if (!tx) {
674 0 : return NS_ERROR_FAILURE;
675 : }
676 :
677 6 : nsCOMPtr<nsITransaction> tint = tx->GetTransaction();
678 3 : 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 0 : int32_t nc = 0;
682 0 : tx->GetNumberOfChildren(&nc);
683 0 : if (!nc) {
684 0 : 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 3 : bool isTransient = false;
691 3 : nsresult rv = NS_OK;
692 3 : if (tint) {
693 3 : rv = tint->GetIsTransient(&isTransient);
694 : }
695 3 : 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 3 : 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 0 : RefPtr<nsTransactionItem> top = mDoStack.Peek();
705 0 : if (top) {
706 0 : return top->AddChild(tx); // XXX: What do we do if this fails?
707 : }
708 :
709 : // The transaction succeeded, so clear the redo stack.
710 0 : rv = ClearRedoStack();
711 0 : 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 0 : top = mUndoStack.Peek();
718 0 : if (tint && top) {
719 0 : bool didMerge = false;
720 0 : nsCOMPtr<nsITransaction> topTransaction = top->GetTransaction();
721 0 : if (topTransaction) {
722 0 : bool doInterrupt = false;
723 0 : rv = WillMergeNotify(topTransaction, tint, &doInterrupt);
724 0 : NS_ENSURE_SUCCESS(rv, rv);
725 :
726 0 : if (!doInterrupt) {
727 0 : rv = topTransaction->Merge(tint, &didMerge);
728 0 : nsresult rv2 = DidMergeNotify(topTransaction, tint, didMerge, rv);
729 0 : if (NS_SUCCEEDED(rv)) {
730 0 : rv = rv2;
731 : }
732 0 : if (NS_FAILED(rv)) {
733 : // XXX: What do we do if this fails?
734 : }
735 0 : if (didMerge) {
736 0 : 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 0 : int32_t sz = mUndoStack.GetSize();
745 0 : if (mMaxTransactionCount > 0 && sz >= mMaxTransactionCount) {
746 0 : RefPtr<nsTransactionItem> overflow = mUndoStack.PopBottom();
747 : }
748 :
749 : // Push the transaction on the undo stack:
750 0 : mUndoStack.Push(tx.forget());
751 0 : return NS_OK;
752 : }
|