TransactionItem.cpp (8035B)
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 "TransactionItem.h" 7 8 #include "mozilla/mozalloc.h" 9 #include "mozilla/DebugOnly.h" 10 #include "mozilla/IntegerRange.h" 11 #include "mozilla/OwningNonNull.h" 12 #include "mozilla/TransactionManager.h" 13 #include "mozilla/TransactionStack.h" 14 #include "nsCOMPtr.h" 15 #include "nsDebug.h" 16 #include "nsError.h" 17 #include "nsISupportsImpl.h" 18 #include "nsITransaction.h" 19 20 namespace mozilla { 21 22 TransactionItem::TransactionItem(nsITransaction* aTransaction) 23 : mTransaction(aTransaction), mUndoStack(0), mRedoStack(0) {} 24 25 TransactionItem::~TransactionItem() { 26 delete mRedoStack; 27 delete mUndoStack; 28 } 29 30 void TransactionItem::CleanUp() { 31 mData.Clear(); 32 mTransaction = nullptr; 33 if (mRedoStack) { 34 mRedoStack->DoUnlink(); 35 } 36 if (mUndoStack) { 37 mUndoStack->DoUnlink(); 38 } 39 } 40 41 NS_IMPL_CYCLE_COLLECTING_NATIVE_ADDREF(TransactionItem) 42 NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE_WITH_LAST_RELEASE(TransactionItem, 43 CleanUp()) 44 45 NS_IMPL_CYCLE_COLLECTION_CLASS(TransactionItem) 46 47 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(TransactionItem) 48 tmp->CleanUp(); 49 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 50 51 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(TransactionItem) 52 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mData) 53 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTransaction) 54 if (tmp->mRedoStack) { 55 tmp->mRedoStack->DoTraverse(cb); 56 } 57 if (tmp->mUndoStack) { 58 tmp->mUndoStack->DoTraverse(cb); 59 } 60 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 61 62 nsresult TransactionItem::AddChild(TransactionItem& aTransactionItem) { 63 if (!mUndoStack) { 64 mUndoStack = new TransactionStack(TransactionStack::FOR_UNDO); 65 } 66 67 mUndoStack->Push(&aTransactionItem); 68 return NS_OK; 69 } 70 71 already_AddRefed<nsITransaction> TransactionItem::GetTransaction() { 72 return do_AddRef(mTransaction); 73 } 74 75 nsresult TransactionItem::DoTransaction() { 76 if (!mTransaction) { 77 return NS_OK; 78 } 79 OwningNonNull<nsITransaction> transaction = *mTransaction; 80 nsresult rv = transaction->DoTransaction(); 81 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 82 "nsITransaction::DoTransaction() failed"); 83 return rv; 84 } 85 86 nsresult TransactionItem::UndoTransaction( 87 TransactionManager* aTransactionManager) { 88 nsresult rv = UndoChildren(aTransactionManager); 89 if (NS_FAILED(rv)) { 90 NS_WARNING("TransactionItem::UndoChildren() failed"); 91 DebugOnly<nsresult> rvIgnored = RecoverFromUndoError(aTransactionManager); 92 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 93 "TransactionItem::RecoverFromUndoError() failed"); 94 return rv; 95 } 96 97 if (!mTransaction) { 98 return NS_OK; 99 } 100 101 OwningNonNull<nsITransaction> transaction = *mTransaction; 102 rv = transaction->UndoTransaction(); 103 if (NS_SUCCEEDED(rv)) { 104 return NS_OK; 105 } 106 107 NS_WARNING("TransactionItem::UndoTransaction() failed"); 108 DebugOnly<nsresult> rvIgnored = RecoverFromUndoError(aTransactionManager); 109 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 110 "TransactionItem::RecoverFromUndoError() failed"); 111 return rv; 112 } 113 114 nsresult TransactionItem::UndoChildren( 115 TransactionManager* aTransactionManager) { 116 if (!mUndoStack) { 117 return NS_OK; 118 } 119 120 if (!mRedoStack) { 121 mRedoStack = new TransactionStack(TransactionStack::FOR_REDO); 122 } 123 124 const size_t undoStackSize = mUndoStack->GetSize(); 125 126 nsresult rv = NS_OK; 127 for ([[maybe_unused]] const size_t undoneCount : 128 IntegerRange(undoStackSize)) { 129 RefPtr<TransactionItem> transactionItem = mUndoStack->Peek(); 130 if (!transactionItem) { 131 return NS_ERROR_FAILURE; 132 } 133 134 nsCOMPtr<nsITransaction> transaction = transactionItem->GetTransaction(); 135 rv = transactionItem->UndoTransaction(aTransactionManager); 136 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 137 "TransactionItem::UndoTransaction() failed"); 138 if (NS_SUCCEEDED(rv)) { 139 transactionItem = mUndoStack->Pop(); 140 mRedoStack->Push(transactionItem.forget()); 141 } 142 143 if (transaction) { 144 aTransactionManager->DidUndoNotify(*transaction, rv); 145 } 146 } 147 // NS_OK if there is no Undo items or all methods work fine, otherwise, 148 // the result of the last item's UndoTransaction(). 149 return rv; 150 } 151 152 nsresult TransactionItem::RedoTransaction( 153 TransactionManager* aTransactionManager) { 154 nsCOMPtr<nsITransaction> transaction(mTransaction); 155 if (transaction) { 156 nsresult rv = transaction->RedoTransaction(); 157 if (NS_FAILED(rv)) { 158 NS_WARNING("nsITransaction::RedoTransaction() failed"); 159 return rv; 160 } 161 } 162 163 nsresult rv = RedoChildren(aTransactionManager); 164 if (NS_SUCCEEDED(rv)) { 165 return NS_OK; 166 } 167 168 NS_WARNING("TransactionItem::RedoChildren() failed"); 169 DebugOnly<nsresult> rvIgnored = RecoverFromRedoError(aTransactionManager); 170 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 171 "TransactionItem::RecoverFromRedoError() failed"); 172 return rv; 173 } 174 175 nsresult TransactionItem::RedoChildren( 176 TransactionManager* aTransactionManager) { 177 if (!mRedoStack) { 178 return NS_OK; 179 } 180 181 /* Redo all of the transaction items children! */ 182 const size_t redoStackSize = mRedoStack->GetSize(); 183 184 nsresult rv = NS_OK; 185 for ([[maybe_unused]] const size_t redoneCount : 186 IntegerRange(redoStackSize)) { 187 RefPtr<TransactionItem> transactionItem = mRedoStack->Peek(); 188 if (NS_WARN_IF(!transactionItem)) { 189 return NS_ERROR_FAILURE; 190 } 191 192 nsCOMPtr<nsITransaction> transaction = transactionItem->GetTransaction(); 193 rv = transactionItem->RedoTransaction(aTransactionManager); 194 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 195 "TransactionItem::RedoTransaction() failed"); 196 if (NS_SUCCEEDED(rv)) { 197 transactionItem = mRedoStack->Pop(); 198 mUndoStack->Push(transactionItem.forget()); 199 } 200 201 // XXX Shouldn't this DidRedoNotify()? (bug 1311626) 202 if (transaction) { 203 aTransactionManager->DidUndoNotify(*transaction, rv); 204 } 205 } 206 // NS_OK if there is no Redo items or all methods work fine, otherwise, 207 // the result of the last item's RedoTransaction(). 208 return rv; 209 } 210 211 size_t TransactionItem::NumberOfUndoItems() const { 212 NS_WARNING_ASSERTION(!mUndoStack || mUndoStack->GetSize() > 0, 213 "UndoStack cannot have no children"); 214 return mUndoStack ? mUndoStack->GetSize() : 0; 215 } 216 217 size_t TransactionItem::NumberOfRedoItems() const { 218 NS_WARNING_ASSERTION(!mRedoStack || mRedoStack->GetSize() > 0, 219 "UndoStack cannot have no children"); 220 return mRedoStack ? mRedoStack->GetSize() : 0; 221 } 222 223 nsresult TransactionItem::RecoverFromUndoError( 224 TransactionManager* aTransactionManager) { 225 // If this method gets called, we never got to the point where we 226 // successfully called UndoTransaction() for the transaction item itself. 227 // Just redo any children that successfully called undo! 228 nsresult rv = RedoChildren(aTransactionManager); 229 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 230 "TransactionItem::RedoChildren() failed"); 231 return rv; 232 } 233 234 nsresult TransactionItem::RecoverFromRedoError( 235 TransactionManager* aTransactionManager) { 236 // If this method gets called, we already successfully called 237 // RedoTransaction() for the transaction item itself. Undo all 238 // the children that successfully called RedoTransaction(), 239 // then undo the transaction item itself. 240 nsresult rv = UndoChildren(aTransactionManager); 241 if (NS_FAILED(rv)) { 242 NS_WARNING("TransactionItem::UndoChildren() failed"); 243 return rv; 244 } 245 246 if (!mTransaction) { 247 return NS_OK; 248 } 249 250 OwningNonNull<nsITransaction> transaction = *mTransaction; 251 rv = transaction->UndoTransaction(); 252 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 253 "nsITransaction::UndoTransaction() failed"); 254 return rv; 255 } 256 257 } // namespace mozilla