1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/editor/txmgr/src/nsTransactionManager.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,905 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "mozilla/Assertions.h" 1.10 +#include "mozilla/mozalloc.h" 1.11 +#include "nsAutoPtr.h" 1.12 +#include "nsCOMPtr.h" 1.13 +#include "nsDebug.h" 1.14 +#include "nsError.h" 1.15 +#include "nsISupportsBase.h" 1.16 +#include "nsISupportsUtils.h" 1.17 +#include "nsITransaction.h" 1.18 +#include "nsITransactionList.h" 1.19 +#include "nsITransactionListener.h" 1.20 +#include "nsIWeakReference.h" 1.21 +#include "nsTransactionItem.h" 1.22 +#include "nsTransactionList.h" 1.23 +#include "nsTransactionManager.h" 1.24 +#include "nsTransactionStack.h" 1.25 + 1.26 +nsTransactionManager::nsTransactionManager(int32_t aMaxTransactionCount) 1.27 + : mMaxTransactionCount(aMaxTransactionCount) 1.28 + , mDoStack(nsTransactionStack::FOR_UNDO) 1.29 + , mUndoStack(nsTransactionStack::FOR_UNDO) 1.30 + , mRedoStack(nsTransactionStack::FOR_REDO) 1.31 +{ 1.32 +} 1.33 + 1.34 +nsTransactionManager::~nsTransactionManager() 1.35 +{ 1.36 +} 1.37 + 1.38 +NS_IMPL_CYCLE_COLLECTION_CLASS(nsTransactionManager) 1.39 + 1.40 +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsTransactionManager) 1.41 + NS_IMPL_CYCLE_COLLECTION_UNLINK(mListeners) 1.42 + tmp->mDoStack.DoUnlink(); 1.43 + tmp->mUndoStack.DoUnlink(); 1.44 + tmp->mRedoStack.DoUnlink(); 1.45 +NS_IMPL_CYCLE_COLLECTION_UNLINK_END 1.46 + 1.47 +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsTransactionManager) 1.48 + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListeners) 1.49 + tmp->mDoStack.DoTraverse(cb); 1.50 + tmp->mUndoStack.DoTraverse(cb); 1.51 + tmp->mRedoStack.DoTraverse(cb); 1.52 +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 1.53 + 1.54 +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTransactionManager) 1.55 + NS_INTERFACE_MAP_ENTRY(nsITransactionManager) 1.56 + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) 1.57 + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITransactionManager) 1.58 +NS_INTERFACE_MAP_END 1.59 + 1.60 +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTransactionManager) 1.61 +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTransactionManager) 1.62 + 1.63 +NS_IMETHODIMP 1.64 +nsTransactionManager::DoTransaction(nsITransaction *aTransaction) 1.65 +{ 1.66 + nsresult result; 1.67 + 1.68 + NS_ENSURE_TRUE(aTransaction, NS_ERROR_NULL_POINTER); 1.69 + 1.70 + bool doInterrupt = false; 1.71 + 1.72 + result = WillDoNotify(aTransaction, &doInterrupt); 1.73 + 1.74 + if (NS_FAILED(result)) { 1.75 + return result; 1.76 + } 1.77 + 1.78 + if (doInterrupt) { 1.79 + return NS_OK; 1.80 + } 1.81 + 1.82 + result = BeginTransaction(aTransaction, nullptr); 1.83 + 1.84 + if (NS_FAILED(result)) { 1.85 + DidDoNotify(aTransaction, result); 1.86 + return result; 1.87 + } 1.88 + 1.89 + result = EndTransaction(false); 1.90 + 1.91 + nsresult result2 = DidDoNotify(aTransaction, result); 1.92 + 1.93 + if (NS_SUCCEEDED(result)) 1.94 + result = result2; 1.95 + 1.96 + return result; 1.97 +} 1.98 + 1.99 +NS_IMETHODIMP 1.100 +nsTransactionManager::UndoTransaction() 1.101 +{ 1.102 + nsresult result = NS_OK; 1.103 + 1.104 + // It is illegal to call UndoTransaction() while the transaction manager is 1.105 + // executing a transaction's DoTransaction() method! If this happens, 1.106 + // the UndoTransaction() request is ignored, and we return NS_ERROR_FAILURE. 1.107 + 1.108 + nsRefPtr<nsTransactionItem> tx = mDoStack.Peek(); 1.109 + 1.110 + if (tx) { 1.111 + return NS_ERROR_FAILURE; 1.112 + } 1.113 + 1.114 + // Peek at the top of the undo stack. Don't remove the transaction 1.115 + // until it has successfully completed. 1.116 + tx = mUndoStack.Peek(); 1.117 + 1.118 + // Bail if there's nothing on the stack. 1.119 + if (!tx) { 1.120 + return NS_OK; 1.121 + } 1.122 + 1.123 + nsCOMPtr<nsITransaction> t = tx->GetTransaction(); 1.124 + 1.125 + bool doInterrupt = false; 1.126 + 1.127 + result = WillUndoNotify(t, &doInterrupt); 1.128 + 1.129 + if (NS_FAILED(result)) { 1.130 + return result; 1.131 + } 1.132 + 1.133 + if (doInterrupt) { 1.134 + return NS_OK; 1.135 + } 1.136 + 1.137 + result = tx->UndoTransaction(this); 1.138 + 1.139 + if (NS_SUCCEEDED(result)) { 1.140 + tx = mUndoStack.Pop(); 1.141 + mRedoStack.Push(tx); 1.142 + } 1.143 + 1.144 + nsresult result2 = DidUndoNotify(t, result); 1.145 + 1.146 + if (NS_SUCCEEDED(result)) 1.147 + result = result2; 1.148 + 1.149 + return result; 1.150 +} 1.151 + 1.152 +NS_IMETHODIMP 1.153 +nsTransactionManager::RedoTransaction() 1.154 +{ 1.155 + nsresult result = NS_OK; 1.156 + 1.157 + // It is illegal to call RedoTransaction() while the transaction manager is 1.158 + // executing a transaction's DoTransaction() method! If this happens, 1.159 + // the RedoTransaction() request is ignored, and we return NS_ERROR_FAILURE. 1.160 + 1.161 + nsRefPtr<nsTransactionItem> tx = mDoStack.Peek(); 1.162 + 1.163 + if (tx) { 1.164 + return NS_ERROR_FAILURE; 1.165 + } 1.166 + 1.167 + // Peek at the top of the redo stack. Don't remove the transaction 1.168 + // until it has successfully completed. 1.169 + tx = mRedoStack.Peek(); 1.170 + 1.171 + // Bail if there's nothing on the stack. 1.172 + if (!tx) { 1.173 + return NS_OK; 1.174 + } 1.175 + 1.176 + nsCOMPtr<nsITransaction> t = tx->GetTransaction(); 1.177 + 1.178 + bool doInterrupt = false; 1.179 + 1.180 + result = WillRedoNotify(t, &doInterrupt); 1.181 + 1.182 + if (NS_FAILED(result)) { 1.183 + return result; 1.184 + } 1.185 + 1.186 + if (doInterrupt) { 1.187 + return NS_OK; 1.188 + } 1.189 + 1.190 + result = tx->RedoTransaction(this); 1.191 + 1.192 + if (NS_SUCCEEDED(result)) { 1.193 + tx = mRedoStack.Pop(); 1.194 + mUndoStack.Push(tx); 1.195 + } 1.196 + 1.197 + nsresult result2 = DidRedoNotify(t, result); 1.198 + 1.199 + if (NS_SUCCEEDED(result)) 1.200 + result = result2; 1.201 + 1.202 + return result; 1.203 +} 1.204 + 1.205 +NS_IMETHODIMP 1.206 +nsTransactionManager::Clear() 1.207 +{ 1.208 + nsresult result; 1.209 + 1.210 + result = ClearRedoStack(); 1.211 + 1.212 + if (NS_FAILED(result)) { 1.213 + return result; 1.214 + } 1.215 + 1.216 + result = ClearUndoStack(); 1.217 + 1.218 + return result; 1.219 +} 1.220 + 1.221 +NS_IMETHODIMP 1.222 +nsTransactionManager::BeginBatch(nsISupports* aData) 1.223 +{ 1.224 + nsresult result; 1.225 + 1.226 + // We can batch independent transactions together by simply pushing 1.227 + // a dummy transaction item on the do stack. This dummy transaction item 1.228 + // will be popped off the do stack, and then pushed on the undo stack 1.229 + // in EndBatch(). 1.230 + 1.231 + bool doInterrupt = false; 1.232 + 1.233 + result = WillBeginBatchNotify(&doInterrupt); 1.234 + 1.235 + if (NS_FAILED(result)) { 1.236 + return result; 1.237 + } 1.238 + 1.239 + if (doInterrupt) { 1.240 + return NS_OK; 1.241 + } 1.242 + 1.243 + result = BeginTransaction(0, aData); 1.244 + 1.245 + nsresult result2 = DidBeginBatchNotify(result); 1.246 + 1.247 + if (NS_SUCCEEDED(result)) 1.248 + result = result2; 1.249 + 1.250 + return result; 1.251 +} 1.252 + 1.253 +NS_IMETHODIMP 1.254 +nsTransactionManager::EndBatch(bool aAllowEmpty) 1.255 +{ 1.256 + nsCOMPtr<nsITransaction> ti; 1.257 + nsresult result; 1.258 + 1.259 + // XXX: Need to add some mechanism to detect the case where the transaction 1.260 + // at the top of the do stack isn't the dummy transaction, so we can 1.261 + // throw an error!! This can happen if someone calls EndBatch() within 1.262 + // the DoTransaction() method of a transaction. 1.263 + // 1.264 + // For now, we can detect this case by checking the value of the 1.265 + // dummy transaction's mTransaction field. If it is our dummy 1.266 + // transaction, it should be nullptr. This may not be true in the 1.267 + // future when we allow users to execute a transaction when beginning 1.268 + // a batch!!!! 1.269 + 1.270 + nsRefPtr<nsTransactionItem> tx = mDoStack.Peek(); 1.271 + 1.272 + if (tx) { 1.273 + ti = tx->GetTransaction(); 1.274 + } 1.275 + 1.276 + if (!tx || ti) { 1.277 + return NS_ERROR_FAILURE; 1.278 + } 1.279 + 1.280 + bool doInterrupt = false; 1.281 + 1.282 + result = WillEndBatchNotify(&doInterrupt); 1.283 + 1.284 + if (NS_FAILED(result)) { 1.285 + return result; 1.286 + } 1.287 + 1.288 + if (doInterrupt) { 1.289 + return NS_OK; 1.290 + } 1.291 + 1.292 + result = EndTransaction(aAllowEmpty); 1.293 + 1.294 + nsresult result2 = DidEndBatchNotify(result); 1.295 + 1.296 + if (NS_SUCCEEDED(result)) 1.297 + result = result2; 1.298 + 1.299 + return result; 1.300 +} 1.301 + 1.302 +NS_IMETHODIMP 1.303 +nsTransactionManager::GetNumberOfUndoItems(int32_t *aNumItems) 1.304 +{ 1.305 + *aNumItems = mUndoStack.GetSize(); 1.306 + return NS_OK; 1.307 +} 1.308 + 1.309 +NS_IMETHODIMP 1.310 +nsTransactionManager::GetNumberOfRedoItems(int32_t *aNumItems) 1.311 +{ 1.312 + *aNumItems = mRedoStack.GetSize(); 1.313 + return NS_OK; 1.314 +} 1.315 + 1.316 +NS_IMETHODIMP 1.317 +nsTransactionManager::GetMaxTransactionCount(int32_t *aMaxCount) 1.318 +{ 1.319 + NS_ENSURE_TRUE(aMaxCount, NS_ERROR_NULL_POINTER); 1.320 + 1.321 + *aMaxCount = mMaxTransactionCount; 1.322 + 1.323 + return NS_OK; 1.324 +} 1.325 + 1.326 +NS_IMETHODIMP 1.327 +nsTransactionManager::SetMaxTransactionCount(int32_t aMaxCount) 1.328 +{ 1.329 + int32_t numUndoItems = 0, numRedoItems = 0, total = 0; 1.330 + 1.331 + // It is illegal to call SetMaxTransactionCount() while the transaction 1.332 + // manager is executing a transaction's DoTransaction() method because 1.333 + // the undo and redo stacks might get pruned! If this happens, the 1.334 + // SetMaxTransactionCount() request is ignored, and we return 1.335 + // NS_ERROR_FAILURE. 1.336 + 1.337 + nsRefPtr<nsTransactionItem> tx = mDoStack.Peek(); 1.338 + 1.339 + if (tx) { 1.340 + return NS_ERROR_FAILURE; 1.341 + } 1.342 + 1.343 + // If aMaxCount is less than zero, the user wants unlimited 1.344 + // levels of undo! No need to prune the undo or redo stacks! 1.345 + 1.346 + if (aMaxCount < 0) { 1.347 + mMaxTransactionCount = -1; 1.348 + return NS_OK; 1.349 + } 1.350 + 1.351 + numUndoItems = mUndoStack.GetSize(); 1.352 + 1.353 + numRedoItems = mRedoStack.GetSize(); 1.354 + 1.355 + total = numUndoItems + numRedoItems; 1.356 + 1.357 + // If aMaxCount is greater than the number of transactions that currently 1.358 + // exist on the undo and redo stack, there is no need to prune the 1.359 + // undo or redo stacks! 1.360 + 1.361 + if (aMaxCount > total ) { 1.362 + mMaxTransactionCount = aMaxCount; 1.363 + return NS_OK; 1.364 + } 1.365 + 1.366 + // Try getting rid of some transactions on the undo stack! Start at 1.367 + // the bottom of the stack and pop towards the top. 1.368 + 1.369 + while (numUndoItems > 0 && (numRedoItems + numUndoItems) > aMaxCount) { 1.370 + tx = mUndoStack.PopBottom(); 1.371 + 1.372 + if (!tx) { 1.373 + return NS_ERROR_FAILURE; 1.374 + } 1.375 + 1.376 + --numUndoItems; 1.377 + } 1.378 + 1.379 + // If necessary, get rid of some transactions on the redo stack! Start at 1.380 + // the bottom of the stack and pop towards the top. 1.381 + 1.382 + while (numRedoItems > 0 && (numRedoItems + numUndoItems) > aMaxCount) { 1.383 + tx = mRedoStack.PopBottom(); 1.384 + 1.385 + if (!tx) { 1.386 + return NS_ERROR_FAILURE; 1.387 + } 1.388 + 1.389 + --numRedoItems; 1.390 + } 1.391 + 1.392 + mMaxTransactionCount = aMaxCount; 1.393 + 1.394 + return NS_OK; 1.395 +} 1.396 + 1.397 +NS_IMETHODIMP 1.398 +nsTransactionManager::PeekUndoStack(nsITransaction **aTransaction) 1.399 +{ 1.400 + MOZ_ASSERT(aTransaction); 1.401 + *aTransaction = PeekUndoStack().take(); 1.402 + return NS_OK; 1.403 +} 1.404 + 1.405 +already_AddRefed<nsITransaction> 1.406 +nsTransactionManager::PeekUndoStack() 1.407 +{ 1.408 + nsRefPtr<nsTransactionItem> tx = mUndoStack.Peek(); 1.409 + 1.410 + if (!tx) { 1.411 + return nullptr; 1.412 + } 1.413 + 1.414 + return tx->GetTransaction(); 1.415 +} 1.416 + 1.417 +NS_IMETHODIMP 1.418 +nsTransactionManager::PeekRedoStack(nsITransaction** aTransaction) 1.419 +{ 1.420 + MOZ_ASSERT(aTransaction); 1.421 + *aTransaction = PeekRedoStack().take(); 1.422 + return NS_OK; 1.423 +} 1.424 + 1.425 +already_AddRefed<nsITransaction> 1.426 +nsTransactionManager::PeekRedoStack() 1.427 +{ 1.428 + nsRefPtr<nsTransactionItem> tx = mRedoStack.Peek(); 1.429 + 1.430 + if (!tx) { 1.431 + return nullptr; 1.432 + } 1.433 + 1.434 + return tx->GetTransaction(); 1.435 +} 1.436 + 1.437 +NS_IMETHODIMP 1.438 +nsTransactionManager::GetUndoList(nsITransactionList **aTransactionList) 1.439 +{ 1.440 + NS_ENSURE_TRUE(aTransactionList, NS_ERROR_NULL_POINTER); 1.441 + 1.442 + *aTransactionList = (nsITransactionList *)new nsTransactionList(this, &mUndoStack); 1.443 + 1.444 + NS_IF_ADDREF(*aTransactionList); 1.445 + 1.446 + return (! *aTransactionList) ? NS_ERROR_OUT_OF_MEMORY : NS_OK; 1.447 +} 1.448 + 1.449 +NS_IMETHODIMP 1.450 +nsTransactionManager::GetRedoList(nsITransactionList **aTransactionList) 1.451 +{ 1.452 + NS_ENSURE_TRUE(aTransactionList, NS_ERROR_NULL_POINTER); 1.453 + 1.454 + *aTransactionList = (nsITransactionList *)new nsTransactionList(this, &mRedoStack); 1.455 + 1.456 + NS_IF_ADDREF(*aTransactionList); 1.457 + 1.458 + return (! *aTransactionList) ? NS_ERROR_OUT_OF_MEMORY : NS_OK; 1.459 +} 1.460 + 1.461 +nsresult 1.462 +nsTransactionManager::BatchTopUndo() 1.463 +{ 1.464 + if (mUndoStack.GetSize() < 2) { 1.465 + // Not enough transactions to merge into one batch. 1.466 + return NS_OK; 1.467 + } 1.468 + 1.469 + nsRefPtr<nsTransactionItem> lastUndo; 1.470 + nsRefPtr<nsTransactionItem> previousUndo; 1.471 + 1.472 + lastUndo = mUndoStack.Pop(); 1.473 + MOZ_ASSERT(lastUndo, "There should be at least two transactions."); 1.474 + 1.475 + previousUndo = mUndoStack.Peek(); 1.476 + MOZ_ASSERT(previousUndo, "There should be at least two transactions."); 1.477 + 1.478 + nsresult result = previousUndo->AddChild(lastUndo); 1.479 + 1.480 + // Transfer data from the transactions that is going to be 1.481 + // merged to the transaction that it is being merged with. 1.482 + nsCOMArray<nsISupports>& lastData = lastUndo->GetData(); 1.483 + nsCOMArray<nsISupports>& previousData = previousUndo->GetData(); 1.484 + NS_ENSURE_TRUE(previousData.AppendObjects(lastData), NS_ERROR_UNEXPECTED); 1.485 + lastData.Clear(); 1.486 + 1.487 + return result; 1.488 +} 1.489 + 1.490 +nsresult 1.491 +nsTransactionManager::RemoveTopUndo() 1.492 +{ 1.493 + nsRefPtr<nsTransactionItem> lastUndo; 1.494 + 1.495 + lastUndo = mUndoStack.Peek(); 1.496 + if (!lastUndo) { 1.497 + return NS_OK; 1.498 + } 1.499 + 1.500 + lastUndo = mUndoStack.Pop(); 1.501 + 1.502 + return NS_OK; 1.503 +} 1.504 + 1.505 +NS_IMETHODIMP 1.506 +nsTransactionManager::AddListener(nsITransactionListener *aListener) 1.507 +{ 1.508 + NS_ENSURE_TRUE(aListener, NS_ERROR_NULL_POINTER); 1.509 + 1.510 + return mListeners.AppendObject(aListener) ? NS_OK : NS_ERROR_FAILURE; 1.511 +} 1.512 + 1.513 +NS_IMETHODIMP 1.514 +nsTransactionManager::RemoveListener(nsITransactionListener *aListener) 1.515 +{ 1.516 + NS_ENSURE_TRUE(aListener, NS_ERROR_NULL_POINTER); 1.517 + 1.518 + return mListeners.RemoveObject(aListener) ? NS_OK : NS_ERROR_FAILURE; 1.519 +} 1.520 + 1.521 +NS_IMETHODIMP 1.522 +nsTransactionManager::ClearUndoStack() 1.523 +{ 1.524 + mUndoStack.Clear(); 1.525 + return NS_OK; 1.526 +} 1.527 + 1.528 +NS_IMETHODIMP 1.529 +nsTransactionManager::ClearRedoStack() 1.530 +{ 1.531 + mRedoStack.Clear(); 1.532 + return NS_OK; 1.533 +} 1.534 + 1.535 +nsresult 1.536 +nsTransactionManager::WillDoNotify(nsITransaction *aTransaction, bool *aInterrupt) 1.537 +{ 1.538 + nsresult result = NS_OK; 1.539 + for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) 1.540 + { 1.541 + nsITransactionListener *listener = mListeners[i]; 1.542 + 1.543 + NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE); 1.544 + 1.545 + result = listener->WillDo(this, aTransaction, aInterrupt); 1.546 + 1.547 + if (NS_FAILED(result) || *aInterrupt) 1.548 + break; 1.549 + } 1.550 + 1.551 + return result; 1.552 +} 1.553 + 1.554 +nsresult 1.555 +nsTransactionManager::DidDoNotify(nsITransaction *aTransaction, nsresult aDoResult) 1.556 +{ 1.557 + nsresult result = NS_OK; 1.558 + for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) 1.559 + { 1.560 + nsITransactionListener *listener = mListeners[i]; 1.561 + 1.562 + NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE); 1.563 + 1.564 + result = listener->DidDo(this, aTransaction, aDoResult); 1.565 + 1.566 + if (NS_FAILED(result)) 1.567 + break; 1.568 + } 1.569 + 1.570 + return result; 1.571 +} 1.572 + 1.573 +nsresult 1.574 +nsTransactionManager::WillUndoNotify(nsITransaction *aTransaction, bool *aInterrupt) 1.575 +{ 1.576 + nsresult result = NS_OK; 1.577 + for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) 1.578 + { 1.579 + nsITransactionListener *listener = mListeners[i]; 1.580 + 1.581 + NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE); 1.582 + 1.583 + result = listener->WillUndo(this, aTransaction, aInterrupt); 1.584 + 1.585 + if (NS_FAILED(result) || *aInterrupt) 1.586 + break; 1.587 + } 1.588 + 1.589 + return result; 1.590 +} 1.591 + 1.592 +nsresult 1.593 +nsTransactionManager::DidUndoNotify(nsITransaction *aTransaction, nsresult aUndoResult) 1.594 +{ 1.595 + nsresult result = NS_OK; 1.596 + for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) 1.597 + { 1.598 + nsITransactionListener *listener = mListeners[i]; 1.599 + 1.600 + NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE); 1.601 + 1.602 + result = listener->DidUndo(this, aTransaction, aUndoResult); 1.603 + 1.604 + if (NS_FAILED(result)) 1.605 + break; 1.606 + } 1.607 + 1.608 + return result; 1.609 +} 1.610 + 1.611 +nsresult 1.612 +nsTransactionManager::WillRedoNotify(nsITransaction *aTransaction, bool *aInterrupt) 1.613 +{ 1.614 + nsresult result = NS_OK; 1.615 + for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) 1.616 + { 1.617 + nsITransactionListener *listener = mListeners[i]; 1.618 + 1.619 + NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE); 1.620 + 1.621 + result = listener->WillRedo(this, aTransaction, aInterrupt); 1.622 + 1.623 + if (NS_FAILED(result) || *aInterrupt) 1.624 + break; 1.625 + } 1.626 + 1.627 + return result; 1.628 +} 1.629 + 1.630 +nsresult 1.631 +nsTransactionManager::DidRedoNotify(nsITransaction *aTransaction, nsresult aRedoResult) 1.632 +{ 1.633 + nsresult result = NS_OK; 1.634 + for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) 1.635 + { 1.636 + nsITransactionListener *listener = mListeners[i]; 1.637 + 1.638 + NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE); 1.639 + 1.640 + result = listener->DidRedo(this, aTransaction, aRedoResult); 1.641 + 1.642 + if (NS_FAILED(result)) 1.643 + break; 1.644 + } 1.645 + 1.646 + return result; 1.647 +} 1.648 + 1.649 +nsresult 1.650 +nsTransactionManager::WillBeginBatchNotify(bool *aInterrupt) 1.651 +{ 1.652 + nsresult result = NS_OK; 1.653 + for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) 1.654 + { 1.655 + nsITransactionListener *listener = mListeners[i]; 1.656 + 1.657 + NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE); 1.658 + 1.659 + result = listener->WillBeginBatch(this, aInterrupt); 1.660 + 1.661 + if (NS_FAILED(result) || *aInterrupt) 1.662 + break; 1.663 + } 1.664 + 1.665 + return result; 1.666 +} 1.667 + 1.668 +nsresult 1.669 +nsTransactionManager::DidBeginBatchNotify(nsresult aResult) 1.670 +{ 1.671 + nsresult result = NS_OK; 1.672 + for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) 1.673 + { 1.674 + nsITransactionListener *listener = mListeners[i]; 1.675 + 1.676 + NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE); 1.677 + 1.678 + result = listener->DidBeginBatch(this, aResult); 1.679 + 1.680 + if (NS_FAILED(result)) 1.681 + break; 1.682 + } 1.683 + 1.684 + return result; 1.685 +} 1.686 + 1.687 +nsresult 1.688 +nsTransactionManager::WillEndBatchNotify(bool *aInterrupt) 1.689 +{ 1.690 + nsresult result = NS_OK; 1.691 + for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) 1.692 + { 1.693 + nsITransactionListener *listener = mListeners[i]; 1.694 + 1.695 + NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE); 1.696 + 1.697 + result = listener->WillEndBatch(this, aInterrupt); 1.698 + 1.699 + if (NS_FAILED(result) || *aInterrupt) 1.700 + break; 1.701 + } 1.702 + 1.703 + return result; 1.704 +} 1.705 + 1.706 +nsresult 1.707 +nsTransactionManager::DidEndBatchNotify(nsresult aResult) 1.708 +{ 1.709 + nsresult result = NS_OK; 1.710 + for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) 1.711 + { 1.712 + nsITransactionListener *listener = mListeners[i]; 1.713 + 1.714 + NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE); 1.715 + 1.716 + result = listener->DidEndBatch(this, aResult); 1.717 + 1.718 + if (NS_FAILED(result)) 1.719 + break; 1.720 + } 1.721 + 1.722 + return result; 1.723 +} 1.724 + 1.725 +nsresult 1.726 +nsTransactionManager::WillMergeNotify(nsITransaction *aTop, nsITransaction *aTransaction, bool *aInterrupt) 1.727 +{ 1.728 + nsresult result = NS_OK; 1.729 + for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) 1.730 + { 1.731 + nsITransactionListener *listener = mListeners[i]; 1.732 + 1.733 + NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE); 1.734 + 1.735 + result = listener->WillMerge(this, aTop, aTransaction, aInterrupt); 1.736 + 1.737 + if (NS_FAILED(result) || *aInterrupt) 1.738 + break; 1.739 + } 1.740 + 1.741 + return result; 1.742 +} 1.743 + 1.744 +nsresult 1.745 +nsTransactionManager::DidMergeNotify(nsITransaction *aTop, 1.746 + nsITransaction *aTransaction, 1.747 + bool aDidMerge, 1.748 + nsresult aMergeResult) 1.749 +{ 1.750 + nsresult result = NS_OK; 1.751 + for (int32_t i = 0, lcount = mListeners.Count(); i < lcount; i++) 1.752 + { 1.753 + nsITransactionListener *listener = mListeners[i]; 1.754 + 1.755 + NS_ENSURE_TRUE(listener, NS_ERROR_FAILURE); 1.756 + 1.757 + result = listener->DidMerge(this, aTop, aTransaction, aDidMerge, aMergeResult); 1.758 + 1.759 + if (NS_FAILED(result)) 1.760 + break; 1.761 + } 1.762 + 1.763 + return result; 1.764 +} 1.765 + 1.766 +nsresult 1.767 +nsTransactionManager::BeginTransaction(nsITransaction *aTransaction, 1.768 + nsISupports *aData) 1.769 +{ 1.770 + nsresult result = NS_OK; 1.771 + 1.772 + // XXX: POSSIBLE OPTIMIZATION 1.773 + // We could use a factory that pre-allocates/recycles transaction items. 1.774 + nsRefPtr<nsTransactionItem> tx = new nsTransactionItem(aTransaction); 1.775 + 1.776 + if (aData) { 1.777 + nsCOMArray<nsISupports>& data = tx->GetData(); 1.778 + data.AppendObject(aData); 1.779 + } 1.780 + 1.781 + if (!tx) { 1.782 + return NS_ERROR_OUT_OF_MEMORY; 1.783 + } 1.784 + 1.785 + mDoStack.Push(tx); 1.786 + 1.787 + result = tx->DoTransaction(); 1.788 + 1.789 + if (NS_FAILED(result)) { 1.790 + tx = mDoStack.Pop(); 1.791 + return result; 1.792 + } 1.793 + 1.794 + return NS_OK; 1.795 +} 1.796 + 1.797 +nsresult 1.798 +nsTransactionManager::EndTransaction(bool aAllowEmpty) 1.799 +{ 1.800 + nsresult result = NS_OK; 1.801 + 1.802 + nsRefPtr<nsTransactionItem> tx = mDoStack.Pop(); 1.803 + 1.804 + if (!tx) 1.805 + return NS_ERROR_FAILURE; 1.806 + 1.807 + nsCOMPtr<nsITransaction> tint = tx->GetTransaction(); 1.808 + 1.809 + if (!tint && !aAllowEmpty) { 1.810 + int32_t nc = 0; 1.811 + 1.812 + // If we get here, the transaction must be a dummy batch transaction 1.813 + // created by BeginBatch(). If it contains no children, get rid of it! 1.814 + 1.815 + tx->GetNumberOfChildren(&nc); 1.816 + 1.817 + if (!nc) { 1.818 + return result; 1.819 + } 1.820 + } 1.821 + 1.822 + // Check if the transaction is transient. If it is, there's nothing 1.823 + // more to do, just return. 1.824 + 1.825 + bool isTransient = false; 1.826 + 1.827 + if (tint) 1.828 + result = tint->GetIsTransient(&isTransient); 1.829 + 1.830 + if (NS_FAILED(result) || isTransient || !mMaxTransactionCount) { 1.831 + // XXX: Should we be clearing the redo stack if the transaction 1.832 + // is transient and there is nothing on the do stack? 1.833 + return result; 1.834 + } 1.835 + 1.836 + // Check if there is a transaction on the do stack. If there is, 1.837 + // the current transaction is a "sub" transaction, and should 1.838 + // be added to the transaction at the top of the do stack. 1.839 + 1.840 + nsRefPtr<nsTransactionItem> top = mDoStack.Peek(); 1.841 + if (top) { 1.842 + result = top->AddChild(tx); 1.843 + 1.844 + // XXX: What do we do if this fails? 1.845 + 1.846 + return result; 1.847 + } 1.848 + 1.849 + // The transaction succeeded, so clear the redo stack. 1.850 + 1.851 + result = ClearRedoStack(); 1.852 + 1.853 + if (NS_FAILED(result)) { 1.854 + // XXX: What do we do if this fails? 1.855 + } 1.856 + 1.857 + // Check if we can coalesce this transaction with the one at the top 1.858 + // of the undo stack. 1.859 + 1.860 + top = mUndoStack.Peek(); 1.861 + 1.862 + if (tint && top) { 1.863 + bool didMerge = false; 1.864 + nsCOMPtr<nsITransaction> topTransaction = top->GetTransaction(); 1.865 + 1.866 + if (topTransaction) { 1.867 + 1.868 + bool doInterrupt = false; 1.869 + 1.870 + result = WillMergeNotify(topTransaction, tint, &doInterrupt); 1.871 + 1.872 + NS_ENSURE_SUCCESS(result, result); 1.873 + 1.874 + if (!doInterrupt) { 1.875 + result = topTransaction->Merge(tint, &didMerge); 1.876 + 1.877 + nsresult result2 = DidMergeNotify(topTransaction, tint, didMerge, result); 1.878 + 1.879 + if (NS_SUCCEEDED(result)) 1.880 + result = result2; 1.881 + 1.882 + if (NS_FAILED(result)) { 1.883 + // XXX: What do we do if this fails? 1.884 + } 1.885 + 1.886 + if (didMerge) { 1.887 + return result; 1.888 + } 1.889 + } 1.890 + } 1.891 + } 1.892 + 1.893 + // Check to see if we've hit the max level of undo. If so, 1.894 + // pop the bottom transaction off the undo stack and release it! 1.895 + 1.896 + int32_t sz = mUndoStack.GetSize(); 1.897 + 1.898 + if (mMaxTransactionCount > 0 && sz >= mMaxTransactionCount) { 1.899 + nsRefPtr<nsTransactionItem> overflow = mUndoStack.PopBottom(); 1.900 + } 1.901 + 1.902 + // Push the transaction on the undo stack: 1.903 + 1.904 + mUndoStack.Push(tx); 1.905 + 1.906 + return NS_OK; 1.907 +} 1.908 +