|
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/mozalloc.h" |
|
7 #include "nsAutoPtr.h" |
|
8 #include "nsCOMPtr.h" |
|
9 #include "nsDebug.h" |
|
10 #include "nsError.h" |
|
11 #include "nsISupportsImpl.h" |
|
12 #include "nsITransaction.h" |
|
13 #include "nsTransactionItem.h" |
|
14 #include "nsTransactionManager.h" |
|
15 #include "nsTransactionStack.h" |
|
16 |
|
17 nsTransactionItem::nsTransactionItem(nsITransaction *aTransaction) |
|
18 : mTransaction(aTransaction), mUndoStack(0), mRedoStack(0) |
|
19 { |
|
20 } |
|
21 |
|
22 nsTransactionItem::~nsTransactionItem() |
|
23 { |
|
24 delete mRedoStack; |
|
25 |
|
26 delete mUndoStack; |
|
27 } |
|
28 |
|
29 void |
|
30 nsTransactionItem::CleanUp() |
|
31 { |
|
32 mData.Clear(); |
|
33 mTransaction = nullptr; |
|
34 if (mRedoStack) { |
|
35 mRedoStack->DoUnlink(); |
|
36 } |
|
37 if (mUndoStack) { |
|
38 mUndoStack->DoUnlink(); |
|
39 } |
|
40 } |
|
41 |
|
42 NS_IMPL_CYCLE_COLLECTING_NATIVE_ADDREF(nsTransactionItem) |
|
43 NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE_WITH_LAST_RELEASE(nsTransactionItem, |
|
44 CleanUp()) |
|
45 |
|
46 NS_IMPL_CYCLE_COLLECTION_CLASS(nsTransactionItem) |
|
47 |
|
48 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsTransactionItem) |
|
49 tmp->CleanUp(); |
|
50 NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
|
51 |
|
52 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsTransactionItem) |
|
53 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mData) |
|
54 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTransaction) |
|
55 if (tmp->mRedoStack) { |
|
56 tmp->mRedoStack->DoTraverse(cb); |
|
57 } |
|
58 if (tmp->mUndoStack) { |
|
59 tmp->mUndoStack->DoTraverse(cb); |
|
60 } |
|
61 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
|
62 |
|
63 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsTransactionItem, AddRef) |
|
64 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsTransactionItem, Release) |
|
65 |
|
66 nsresult |
|
67 nsTransactionItem::AddChild(nsTransactionItem *aTransactionItem) |
|
68 { |
|
69 NS_ENSURE_TRUE(aTransactionItem, NS_ERROR_NULL_POINTER); |
|
70 |
|
71 if (!mUndoStack) { |
|
72 mUndoStack = new nsTransactionStack(nsTransactionStack::FOR_UNDO); |
|
73 } |
|
74 |
|
75 mUndoStack->Push(aTransactionItem); |
|
76 |
|
77 return NS_OK; |
|
78 } |
|
79 |
|
80 already_AddRefed<nsITransaction> |
|
81 nsTransactionItem::GetTransaction() |
|
82 { |
|
83 nsCOMPtr<nsITransaction> txn = mTransaction; |
|
84 return txn.forget(); |
|
85 } |
|
86 |
|
87 nsresult |
|
88 nsTransactionItem::GetIsBatch(bool *aIsBatch) |
|
89 { |
|
90 NS_ENSURE_TRUE(aIsBatch, NS_ERROR_NULL_POINTER); |
|
91 |
|
92 *aIsBatch = !mTransaction; |
|
93 |
|
94 return NS_OK; |
|
95 } |
|
96 |
|
97 nsresult |
|
98 nsTransactionItem::GetNumberOfChildren(int32_t *aNumChildren) |
|
99 { |
|
100 nsresult result; |
|
101 |
|
102 NS_ENSURE_TRUE(aNumChildren, NS_ERROR_NULL_POINTER); |
|
103 |
|
104 *aNumChildren = 0; |
|
105 |
|
106 int32_t ui = 0; |
|
107 int32_t ri = 0; |
|
108 |
|
109 result = GetNumberOfUndoItems(&ui); |
|
110 |
|
111 NS_ENSURE_SUCCESS(result, result); |
|
112 |
|
113 result = GetNumberOfRedoItems(&ri); |
|
114 |
|
115 NS_ENSURE_SUCCESS(result, result); |
|
116 |
|
117 *aNumChildren = ui + ri; |
|
118 |
|
119 return NS_OK; |
|
120 } |
|
121 |
|
122 nsresult |
|
123 nsTransactionItem::GetChild(int32_t aIndex, nsTransactionItem **aChild) |
|
124 { |
|
125 NS_ENSURE_TRUE(aChild, NS_ERROR_NULL_POINTER); |
|
126 |
|
127 *aChild = 0; |
|
128 |
|
129 int32_t numItems = 0; |
|
130 nsresult result = GetNumberOfChildren(&numItems); |
|
131 |
|
132 NS_ENSURE_SUCCESS(result, result); |
|
133 |
|
134 if (aIndex < 0 || aIndex >= numItems) |
|
135 return NS_ERROR_FAILURE; |
|
136 |
|
137 // Children are expected to be in the order they were added, |
|
138 // so the child first added would be at the bottom of the undo |
|
139 // stack, or if there are no items on the undo stack, it would |
|
140 // be at the top of the redo stack. |
|
141 |
|
142 result = GetNumberOfUndoItems(&numItems); |
|
143 |
|
144 NS_ENSURE_SUCCESS(result, result); |
|
145 |
|
146 if (numItems > 0 && aIndex < numItems) { |
|
147 NS_ENSURE_TRUE(mUndoStack, NS_ERROR_FAILURE); |
|
148 |
|
149 nsRefPtr<nsTransactionItem> child = mUndoStack->GetItem(aIndex); |
|
150 child.forget(aChild); |
|
151 return *aChild ? NS_OK : NS_ERROR_FAILURE; |
|
152 } |
|
153 |
|
154 // Adjust the index for the redo stack: |
|
155 |
|
156 aIndex -= numItems; |
|
157 |
|
158 result = GetNumberOfRedoItems(&numItems); |
|
159 |
|
160 NS_ENSURE_SUCCESS(result, result); |
|
161 |
|
162 NS_ENSURE_TRUE(mRedoStack && numItems != 0 && aIndex < numItems, NS_ERROR_FAILURE); |
|
163 |
|
164 nsRefPtr<nsTransactionItem> child = mRedoStack->GetItem(aIndex); |
|
165 child.forget(aChild); |
|
166 return *aChild ? NS_OK : NS_ERROR_FAILURE; |
|
167 } |
|
168 |
|
169 nsresult |
|
170 nsTransactionItem::DoTransaction() |
|
171 { |
|
172 if (mTransaction) |
|
173 return mTransaction->DoTransaction(); |
|
174 return NS_OK; |
|
175 } |
|
176 |
|
177 nsresult |
|
178 nsTransactionItem::UndoTransaction(nsTransactionManager *aTxMgr) |
|
179 { |
|
180 nsresult result = UndoChildren(aTxMgr); |
|
181 |
|
182 if (NS_FAILED(result)) { |
|
183 RecoverFromUndoError(aTxMgr); |
|
184 return result; |
|
185 } |
|
186 |
|
187 if (!mTransaction) |
|
188 return NS_OK; |
|
189 |
|
190 result = mTransaction->UndoTransaction(); |
|
191 |
|
192 if (NS_FAILED(result)) { |
|
193 RecoverFromUndoError(aTxMgr); |
|
194 return result; |
|
195 } |
|
196 |
|
197 return NS_OK; |
|
198 } |
|
199 |
|
200 nsresult |
|
201 nsTransactionItem::UndoChildren(nsTransactionManager *aTxMgr) |
|
202 { |
|
203 nsRefPtr<nsTransactionItem> item; |
|
204 nsresult result = NS_OK; |
|
205 int32_t sz = 0; |
|
206 |
|
207 if (mUndoStack) { |
|
208 if (!mRedoStack && mUndoStack) { |
|
209 mRedoStack = new nsTransactionStack(nsTransactionStack::FOR_REDO); |
|
210 } |
|
211 |
|
212 /* Undo all of the transaction items children! */ |
|
213 sz = mUndoStack->GetSize(); |
|
214 |
|
215 while (sz-- > 0) { |
|
216 item = mUndoStack->Peek(); |
|
217 |
|
218 if (!item) { |
|
219 return NS_ERROR_FAILURE; |
|
220 } |
|
221 |
|
222 nsCOMPtr<nsITransaction> t = item->GetTransaction(); |
|
223 |
|
224 bool doInterrupt = false; |
|
225 |
|
226 result = aTxMgr->WillUndoNotify(t, &doInterrupt); |
|
227 |
|
228 if (NS_FAILED(result)) { |
|
229 return result; |
|
230 } |
|
231 |
|
232 if (doInterrupt) { |
|
233 return NS_OK; |
|
234 } |
|
235 |
|
236 result = item->UndoTransaction(aTxMgr); |
|
237 |
|
238 if (NS_SUCCEEDED(result)) { |
|
239 item = mUndoStack->Pop(); |
|
240 mRedoStack->Push(item); |
|
241 } |
|
242 |
|
243 nsresult result2 = aTxMgr->DidUndoNotify(t, result); |
|
244 |
|
245 if (NS_SUCCEEDED(result)) { |
|
246 result = result2; |
|
247 } |
|
248 } |
|
249 } |
|
250 |
|
251 return result; |
|
252 } |
|
253 |
|
254 nsresult |
|
255 nsTransactionItem::RedoTransaction(nsTransactionManager *aTxMgr) |
|
256 { |
|
257 nsresult result; |
|
258 |
|
259 nsCOMPtr<nsITransaction> kungfuDeathGrip(mTransaction); |
|
260 if (mTransaction) { |
|
261 result = mTransaction->RedoTransaction(); |
|
262 |
|
263 NS_ENSURE_SUCCESS(result, result); |
|
264 } |
|
265 |
|
266 result = RedoChildren(aTxMgr); |
|
267 |
|
268 if (NS_FAILED(result)) { |
|
269 RecoverFromRedoError(aTxMgr); |
|
270 return result; |
|
271 } |
|
272 |
|
273 return NS_OK; |
|
274 } |
|
275 |
|
276 nsresult |
|
277 nsTransactionItem::RedoChildren(nsTransactionManager *aTxMgr) |
|
278 { |
|
279 nsRefPtr<nsTransactionItem> item; |
|
280 nsresult result = NS_OK; |
|
281 |
|
282 if (!mRedoStack) |
|
283 return NS_OK; |
|
284 |
|
285 /* Redo all of the transaction items children! */ |
|
286 int32_t sz = mRedoStack->GetSize(); |
|
287 |
|
288 while (sz-- > 0) { |
|
289 item = mRedoStack->Peek(); |
|
290 |
|
291 if (!item) { |
|
292 return NS_ERROR_FAILURE; |
|
293 } |
|
294 |
|
295 nsCOMPtr<nsITransaction> t = item->GetTransaction(); |
|
296 |
|
297 bool doInterrupt = false; |
|
298 |
|
299 result = aTxMgr->WillRedoNotify(t, &doInterrupt); |
|
300 |
|
301 if (NS_FAILED(result)) { |
|
302 return result; |
|
303 } |
|
304 |
|
305 if (doInterrupt) { |
|
306 return NS_OK; |
|
307 } |
|
308 |
|
309 result = item->RedoTransaction(aTxMgr); |
|
310 |
|
311 if (NS_SUCCEEDED(result)) { |
|
312 item = mRedoStack->Pop(); |
|
313 mUndoStack->Push(item); |
|
314 } |
|
315 |
|
316 nsresult result2 = aTxMgr->DidUndoNotify(t, result); |
|
317 |
|
318 if (NS_SUCCEEDED(result)) { |
|
319 result = result2; |
|
320 } |
|
321 } |
|
322 |
|
323 return result; |
|
324 } |
|
325 |
|
326 nsresult |
|
327 nsTransactionItem::GetNumberOfUndoItems(int32_t *aNumItems) |
|
328 { |
|
329 NS_ENSURE_TRUE(aNumItems, NS_ERROR_NULL_POINTER); |
|
330 |
|
331 if (!mUndoStack) { |
|
332 *aNumItems = 0; |
|
333 return NS_OK; |
|
334 } |
|
335 |
|
336 *aNumItems = mUndoStack->GetSize(); |
|
337 return *aNumItems ? NS_OK : NS_ERROR_FAILURE; |
|
338 } |
|
339 |
|
340 nsresult |
|
341 nsTransactionItem::GetNumberOfRedoItems(int32_t *aNumItems) |
|
342 { |
|
343 NS_ENSURE_TRUE(aNumItems, NS_ERROR_NULL_POINTER); |
|
344 |
|
345 if (!mRedoStack) { |
|
346 *aNumItems = 0; |
|
347 return NS_OK; |
|
348 } |
|
349 |
|
350 *aNumItems = mRedoStack->GetSize(); |
|
351 return *aNumItems ? NS_OK : NS_ERROR_FAILURE; |
|
352 } |
|
353 |
|
354 nsresult |
|
355 nsTransactionItem::RecoverFromUndoError(nsTransactionManager *aTxMgr) |
|
356 { |
|
357 // |
|
358 // If this method gets called, we never got to the point where we |
|
359 // successfully called UndoTransaction() for the transaction item itself. |
|
360 // Just redo any children that successfully called undo! |
|
361 // |
|
362 return RedoChildren(aTxMgr); |
|
363 } |
|
364 |
|
365 nsresult |
|
366 nsTransactionItem::RecoverFromRedoError(nsTransactionManager *aTxMgr) |
|
367 { |
|
368 // |
|
369 // If this method gets called, we already successfully called |
|
370 // RedoTransaction() for the transaction item itself. Undo all |
|
371 // the children that successfully called RedoTransaction(), |
|
372 // then undo the transaction item itself. |
|
373 // |
|
374 |
|
375 nsresult result; |
|
376 |
|
377 result = UndoChildren(aTxMgr); |
|
378 |
|
379 if (NS_FAILED(result)) { |
|
380 return result; |
|
381 } |
|
382 |
|
383 if (!mTransaction) |
|
384 return NS_OK; |
|
385 |
|
386 return mTransaction->UndoTransaction(); |
|
387 } |
|
388 |