dom/indexedDB/IDBTransaction.cpp

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:37d2f0efb8f2
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "base/basictypes.h"
8
9 #include "IDBTransaction.h"
10
11 #include "nsIAppShell.h"
12 #include "nsIScriptContext.h"
13
14 #include "mozilla/dom/quota/QuotaManager.h"
15 #include "mozilla/storage.h"
16 #include "nsDOMClassInfoID.h"
17 #include "mozilla/dom/DOMStringList.h"
18 #include "mozilla/EventDispatcher.h"
19 #include "nsPIDOMWindow.h"
20 #include "nsProxyRelease.h"
21 #include "nsThreadUtils.h"
22 #include "nsWidgetsCID.h"
23
24 #include "AsyncConnectionHelper.h"
25 #include "DatabaseInfo.h"
26 #include "IDBCursor.h"
27 #include "IDBEvents.h"
28 #include "IDBFactory.h"
29 #include "IDBObjectStore.h"
30 #include "IndexedDatabaseManager.h"
31 #include "ProfilerHelpers.h"
32 #include "ReportInternalError.h"
33 #include "TransactionThreadPool.h"
34
35 #include "ipc/IndexedDBChild.h"
36
37 #define SAVEPOINT_NAME "savepoint"
38
39 using namespace mozilla;
40 using namespace mozilla::dom;
41 USING_INDEXEDDB_NAMESPACE
42 using mozilla::dom::quota::QuotaManager;
43 using mozilla::ErrorResult;
44
45 namespace {
46
47 NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
48
49 #ifdef MOZ_ENABLE_PROFILER_SPS
50 uint64_t gNextTransactionSerialNumber = 1;
51 #endif
52
53 PLDHashOperator
54 DoomCachedStatements(const nsACString& aQuery,
55 nsCOMPtr<mozIStorageStatement>& aStatement,
56 void* aUserArg)
57 {
58 CommitHelper* helper = static_cast<CommitHelper*>(aUserArg);
59 helper->AddDoomedObject(aStatement);
60 return PL_DHASH_REMOVE;
61 }
62
63 // This runnable doesn't actually do anything beyond "prime the pump" and get
64 // transactions in the right order on the transaction thread pool.
65 class StartTransactionRunnable : public nsIRunnable
66 {
67 public:
68 NS_DECL_ISUPPORTS
69
70 NS_IMETHOD Run()
71 {
72 // NOP
73 return NS_OK;
74 }
75 };
76
77 // Could really use those NS_REFCOUNTING_HAHA_YEAH_RIGHT macros here.
78 NS_IMETHODIMP_(MozExternalRefCountType) StartTransactionRunnable::AddRef()
79 {
80 return 2;
81 }
82
83 NS_IMETHODIMP_(MozExternalRefCountType) StartTransactionRunnable::Release()
84 {
85 return 1;
86 }
87
88 NS_IMPL_QUERY_INTERFACE(StartTransactionRunnable, nsIRunnable)
89
90 } // anonymous namespace
91
92
93 // static
94 already_AddRefed<IDBTransaction>
95 IDBTransaction::CreateInternal(IDBDatabase* aDatabase,
96 const Sequence<nsString>& aObjectStoreNames,
97 Mode aMode,
98 bool aDispatchDelayed,
99 bool aIsVersionChangeTransactionChild)
100 {
101 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
102 NS_ASSERTION(IndexedDatabaseManager::IsMainProcess() || !aDispatchDelayed,
103 "No support for delayed-dispatch transactions in child "
104 "process!");
105 NS_ASSERTION(!aIsVersionChangeTransactionChild ||
106 (!IndexedDatabaseManager::IsMainProcess() &&
107 aMode == IDBTransaction::VERSION_CHANGE),
108 "Busted logic!");
109
110 nsRefPtr<IDBTransaction> transaction = new IDBTransaction(aDatabase);
111
112 transaction->SetScriptOwner(aDatabase->GetScriptOwner());
113 transaction->mDatabase = aDatabase;
114 transaction->mMode = aMode;
115 transaction->mDatabaseInfo = aDatabase->Info();
116 transaction->mObjectStoreNames.AppendElements(aObjectStoreNames);
117 transaction->mObjectStoreNames.Sort();
118
119 IndexedDBTransactionChild* actor = nullptr;
120
121 if (IndexedDatabaseManager::IsMainProcess()) {
122 if (aMode != IDBTransaction::VERSION_CHANGE) {
123 TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate();
124 NS_ENSURE_TRUE(pool, nullptr);
125
126 static StartTransactionRunnable sStartTransactionRunnable;
127 pool->Dispatch(transaction, &sStartTransactionRunnable, false, nullptr);
128 }
129 }
130 else if (!aIsVersionChangeTransactionChild) {
131 IndexedDBDatabaseChild* dbActor = aDatabase->GetActorChild();
132 NS_ASSERTION(dbActor, "Must have an actor here!");
133
134 ipc::NormalTransactionParams params;
135 params.names().AppendElements(aObjectStoreNames);
136 params.mode() = aMode;
137
138 actor = new IndexedDBTransactionChild();
139
140 dbActor->SendPIndexedDBTransactionConstructor(actor, params);
141 }
142
143 if (!aDispatchDelayed) {
144 nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
145 NS_ENSURE_TRUE(appShell, nullptr);
146
147 nsresult rv = appShell->RunBeforeNextEvent(transaction);
148 NS_ENSURE_SUCCESS(rv, nullptr);
149
150 transaction->mCreating = true;
151 }
152
153 if (actor) {
154 NS_ASSERTION(!IndexedDatabaseManager::IsMainProcess(), "Wrong process!");
155 actor->SetTransaction(transaction);
156 }
157
158 return transaction.forget();
159 }
160
161 IDBTransaction::IDBTransaction(IDBDatabase* aDatabase)
162 : IDBWrapperCache(aDatabase),
163 mReadyState(IDBTransaction::INITIAL),
164 mMode(IDBTransaction::READ_ONLY),
165 mPendingRequests(0),
166 mSavepointCount(0),
167 mActorChild(nullptr),
168 mActorParent(nullptr),
169 mAbortCode(NS_OK),
170 #ifdef MOZ_ENABLE_PROFILER_SPS
171 mSerialNumber(gNextTransactionSerialNumber++),
172 #endif
173 mCreating(false)
174 #ifdef DEBUG
175 , mFiredCompleteOrAbort(false)
176 #endif
177 {
178 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
179 }
180
181 IDBTransaction::~IDBTransaction()
182 {
183 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
184 NS_ASSERTION(!mPendingRequests, "Should have no pending requests here!");
185 NS_ASSERTION(!mSavepointCount, "Should have released them all!");
186 NS_ASSERTION(!mConnection, "Should have called CommitOrRollback!");
187 NS_ASSERTION(!mCreating, "Should have been cleared already!");
188 NS_ASSERTION(mFiredCompleteOrAbort, "Should have fired event!");
189
190 NS_ASSERTION(!mActorParent, "Actor parent owns us, how can we be dying?!");
191 if (mActorChild) {
192 NS_ASSERTION(!IndexedDatabaseManager::IsMainProcess(), "Wrong process!");
193 mActorChild->Send__delete__(mActorChild);
194 NS_ASSERTION(!mActorChild, "Should have cleared in Send__delete__!");
195 }
196 }
197
198 void
199 IDBTransaction::OnNewRequest()
200 {
201 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
202 if (!mPendingRequests) {
203 NS_ASSERTION(mReadyState == IDBTransaction::INITIAL,
204 "Reusing a transaction!");
205 mReadyState = IDBTransaction::LOADING;
206 }
207 ++mPendingRequests;
208 }
209
210 void
211 IDBTransaction::OnRequestFinished()
212 {
213 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
214 NS_ASSERTION(mPendingRequests, "Mismatched calls!");
215 --mPendingRequests;
216 if (!mPendingRequests) {
217 NS_ASSERTION(NS_FAILED(mAbortCode) || mReadyState == IDBTransaction::LOADING,
218 "Bad state!");
219 mReadyState = IDBTransaction::COMMITTING;
220 CommitOrRollback();
221 }
222 }
223
224 void
225 IDBTransaction::OnRequestDisconnected()
226 {
227 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
228 NS_ASSERTION(mPendingRequests, "Mismatched calls!");
229 --mPendingRequests;
230 }
231
232 void
233 IDBTransaction::RemoveObjectStore(const nsAString& aName)
234 {
235 NS_ASSERTION(mMode == IDBTransaction::VERSION_CHANGE,
236 "Only remove object stores on VERSION_CHANGE transactions");
237
238 mDatabaseInfo->RemoveObjectStore(aName);
239
240 for (uint32_t i = 0; i < mCreatedObjectStores.Length(); i++) {
241 if (mCreatedObjectStores[i]->Name() == aName) {
242 nsRefPtr<IDBObjectStore> objectStore = mCreatedObjectStores[i];
243 mCreatedObjectStores.RemoveElementAt(i);
244 mDeletedObjectStores.AppendElement(objectStore);
245 break;
246 }
247 }
248 }
249
250 void
251 IDBTransaction::SetTransactionListener(IDBTransactionListener* aListener)
252 {
253 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
254 NS_ASSERTION(!mListener, "Shouldn't already have a listener!");
255 mListener = aListener;
256 }
257
258 nsresult
259 IDBTransaction::CommitOrRollback()
260 {
261 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
262
263 if (!IndexedDatabaseManager::IsMainProcess()) {
264 if (mActorChild) {
265 mActorChild->SendAllRequestsFinished();
266 }
267
268 return NS_OK;
269 }
270
271 nsRefPtr<CommitHelper> helper =
272 new CommitHelper(this, mListener, mCreatedObjectStores);
273
274 TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate();
275 NS_ENSURE_STATE(pool);
276
277 mCachedStatements.Enumerate(DoomCachedStatements, helper);
278 NS_ASSERTION(!mCachedStatements.Count(), "Statements left!");
279
280 nsresult rv = pool->Dispatch(this, helper, true, helper);
281 NS_ENSURE_SUCCESS(rv, rv);
282
283 return NS_OK;
284 }
285
286 bool
287 IDBTransaction::StartSavepoint()
288 {
289 NS_PRECONDITION(!NS_IsMainThread(), "Wrong thread!");
290 NS_PRECONDITION(mConnection, "No connection!");
291
292 nsCOMPtr<mozIStorageStatement> stmt = GetCachedStatement(NS_LITERAL_CSTRING(
293 "SAVEPOINT " SAVEPOINT_NAME
294 ));
295 NS_ENSURE_TRUE(stmt, false);
296
297 mozStorageStatementScoper scoper(stmt);
298
299 nsresult rv = stmt->Execute();
300 NS_ENSURE_SUCCESS(rv, false);
301
302 if (IsWriteAllowed()) {
303 mUpdateFileRefcountFunction->StartSavepoint();
304 }
305
306 ++mSavepointCount;
307
308 return true;
309 }
310
311 nsresult
312 IDBTransaction::ReleaseSavepoint()
313 {
314 NS_PRECONDITION(!NS_IsMainThread(), "Wrong thread!");
315 NS_PRECONDITION(mConnection, "No connection!");
316
317 NS_ASSERTION(mSavepointCount, "Mismatch!");
318
319 nsCOMPtr<mozIStorageStatement> stmt = GetCachedStatement(NS_LITERAL_CSTRING(
320 "RELEASE SAVEPOINT " SAVEPOINT_NAME
321 ));
322 NS_ENSURE_TRUE(stmt, NS_OK);
323
324 mozStorageStatementScoper scoper(stmt);
325
326 nsresult rv = stmt->Execute();
327 NS_ENSURE_SUCCESS(rv, NS_OK);
328
329 if (IsWriteAllowed()) {
330 mUpdateFileRefcountFunction->ReleaseSavepoint();
331 }
332
333 --mSavepointCount;
334
335 return NS_OK;
336 }
337
338 void
339 IDBTransaction::RollbackSavepoint()
340 {
341 NS_PRECONDITION(!NS_IsMainThread(), "Wrong thread!");
342 NS_PRECONDITION(mConnection, "No connection!");
343
344 NS_ASSERTION(mSavepointCount == 1, "Mismatch!");
345 mSavepointCount = 0;
346
347 nsCOMPtr<mozIStorageStatement> stmt = GetCachedStatement(NS_LITERAL_CSTRING(
348 "ROLLBACK TO SAVEPOINT " SAVEPOINT_NAME
349 ));
350 NS_ENSURE_TRUE_VOID(stmt);
351
352 mozStorageStatementScoper scoper(stmt);
353
354 nsresult rv = stmt->Execute();
355 NS_ENSURE_SUCCESS_VOID(rv);
356
357 if (IsWriteAllowed()) {
358 mUpdateFileRefcountFunction->RollbackSavepoint();
359 }
360 }
361
362 nsresult
363 IDBTransaction::GetOrCreateConnection(mozIStorageConnection** aResult)
364 {
365 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
366 NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!");
367
368 PROFILER_LABEL("IndexedDB", "IDBTransaction::GetOrCreateConnection");
369
370 if (mDatabase->IsInvalidated()) {
371 return NS_ERROR_NOT_AVAILABLE;
372 }
373
374 if (!mConnection) {
375 nsCOMPtr<mozIStorageConnection> connection =
376 IDBFactory::GetConnection(mDatabase->FilePath(), mDatabase->Type(),
377 mDatabase->Group(), mDatabase->Origin());
378 NS_ENSURE_TRUE(connection, NS_ERROR_FAILURE);
379
380 nsresult rv;
381
382 nsRefPtr<UpdateRefcountFunction> function;
383 nsCString beginTransaction;
384 if (mMode != IDBTransaction::READ_ONLY) {
385 function = new UpdateRefcountFunction(Database()->Manager());
386 NS_ENSURE_TRUE(function, NS_ERROR_OUT_OF_MEMORY);
387
388 rv = connection->CreateFunction(
389 NS_LITERAL_CSTRING("update_refcount"), 2, function);
390 NS_ENSURE_SUCCESS(rv, rv);
391
392 beginTransaction.AssignLiteral("BEGIN IMMEDIATE TRANSACTION;");
393 }
394 else {
395 beginTransaction.AssignLiteral("BEGIN TRANSACTION;");
396 }
397
398 nsCOMPtr<mozIStorageStatement> stmt;
399 rv = connection->CreateStatement(beginTransaction, getter_AddRefs(stmt));
400 NS_ENSURE_SUCCESS(rv, rv);
401
402 rv = stmt->Execute();
403 NS_ENSURE_SUCCESS(rv, rv);
404
405 function.swap(mUpdateFileRefcountFunction);
406 connection.swap(mConnection);
407 }
408
409 nsCOMPtr<mozIStorageConnection> result(mConnection);
410 result.forget(aResult);
411 return NS_OK;
412 }
413
414 already_AddRefed<mozIStorageStatement>
415 IDBTransaction::GetCachedStatement(const nsACString& aQuery)
416 {
417 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
418 NS_ASSERTION(!aQuery.IsEmpty(), "Empty sql statement!");
419 NS_ASSERTION(mConnection, "No connection!");
420
421 nsCOMPtr<mozIStorageStatement> stmt;
422
423 if (!mCachedStatements.Get(aQuery, getter_AddRefs(stmt))) {
424 nsresult rv = mConnection->CreateStatement(aQuery, getter_AddRefs(stmt));
425 #ifdef DEBUG
426 if (NS_FAILED(rv)) {
427 nsCString error;
428 error.AppendLiteral("The statement `");
429 error.Append(aQuery);
430 error.AppendLiteral("` failed to compile with the error message `");
431 nsCString msg;
432 (void)mConnection->GetLastErrorString(msg);
433 error.Append(msg);
434 error.AppendLiteral("`.");
435 NS_ERROR(error.get());
436 }
437 #endif
438 NS_ENSURE_SUCCESS(rv, nullptr);
439
440 mCachedStatements.Put(aQuery, stmt);
441 }
442
443 return stmt.forget();
444 }
445
446 bool
447 IDBTransaction::IsOpen() const
448 {
449 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
450
451 // If we haven't started anything then we're open.
452 if (mReadyState == IDBTransaction::INITIAL) {
453 return true;
454 }
455
456 // If we've already started then we need to check to see if we still have the
457 // mCreating flag set. If we do (i.e. we haven't returned to the event loop
458 // from the time we were created) then we are open. Otherwise check the
459 // currently running transaction to see if it's the same. We only allow other
460 // requests to be made if this transaction is currently running.
461 if (mReadyState == IDBTransaction::LOADING) {
462 if (mCreating) {
463 return true;
464 }
465
466 if (AsyncConnectionHelper::GetCurrentTransaction() == this) {
467 return true;
468 }
469 }
470
471 return false;
472 }
473
474 already_AddRefed<IDBObjectStore>
475 IDBTransaction::GetOrCreateObjectStore(const nsAString& aName,
476 ObjectStoreInfo* aObjectStoreInfo,
477 bool aCreating)
478 {
479 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
480 NS_ASSERTION(aObjectStoreInfo, "Null pointer!");
481 NS_ASSERTION(!aCreating || GetMode() == IDBTransaction::VERSION_CHANGE,
482 "How else can we create here?!");
483
484 nsRefPtr<IDBObjectStore> retval;
485
486 for (uint32_t index = 0; index < mCreatedObjectStores.Length(); index++) {
487 nsRefPtr<IDBObjectStore>& objectStore = mCreatedObjectStores[index];
488 if (objectStore->Name() == aName) {
489 retval = objectStore;
490 return retval.forget();
491 }
492 }
493
494 retval = IDBObjectStore::Create(this, aObjectStoreInfo, mDatabaseInfo->id,
495 aCreating);
496
497 mCreatedObjectStores.AppendElement(retval);
498
499 return retval.forget();
500 }
501
502 already_AddRefed<FileInfo>
503 IDBTransaction::GetFileInfo(nsIDOMBlob* aBlob)
504 {
505 nsRefPtr<FileInfo> fileInfo;
506 mCreatedFileInfos.Get(aBlob, getter_AddRefs(fileInfo));
507 return fileInfo.forget();
508 }
509
510 void
511 IDBTransaction::AddFileInfo(nsIDOMBlob* aBlob, FileInfo* aFileInfo)
512 {
513 mCreatedFileInfos.Put(aBlob, aFileInfo);
514 }
515
516 void
517 IDBTransaction::ClearCreatedFileInfos()
518 {
519 mCreatedFileInfos.Clear();
520 }
521
522 nsresult
523 IDBTransaction::AbortInternal(nsresult aAbortCode,
524 already_AddRefed<DOMError> aError)
525 {
526 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
527
528 nsRefPtr<DOMError> error = aError;
529
530 if (IsFinished()) {
531 return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
532 }
533
534 if (mActorChild) {
535 NS_ASSERTION(!IndexedDatabaseManager::IsMainProcess(), "Wrong process!");
536 mActorChild->SendAbort(aAbortCode);
537 }
538
539 bool needToCommitOrRollback = mReadyState == IDBTransaction::INITIAL;
540
541 mAbortCode = aAbortCode;
542 mReadyState = IDBTransaction::DONE;
543 mError = error.forget();
544
545 if (GetMode() == IDBTransaction::VERSION_CHANGE) {
546 // If a version change transaction is aborted, we must revert the world
547 // back to its previous state.
548 mDatabase->RevertToPreviousState();
549
550 DatabaseInfo* dbInfo = mDatabase->Info();
551
552 for (uint32_t i = 0; i < mCreatedObjectStores.Length(); i++) {
553 nsRefPtr<IDBObjectStore>& objectStore = mCreatedObjectStores[i];
554 ObjectStoreInfo* info = dbInfo->GetObjectStore(objectStore->Name());
555
556 if (!info) {
557 info = new ObjectStoreInfo(*objectStore->Info());
558 info->indexes.Clear();
559 }
560
561 objectStore->SetInfo(info);
562 }
563
564 for (uint32_t i = 0; i < mDeletedObjectStores.Length(); i++) {
565 nsRefPtr<IDBObjectStore>& objectStore = mDeletedObjectStores[i];
566 ObjectStoreInfo* info = dbInfo->GetObjectStore(objectStore->Name());
567
568 if (!info) {
569 info = new ObjectStoreInfo(*objectStore->Info());
570 info->indexes.Clear();
571 }
572
573 objectStore->SetInfo(info);
574 }
575
576 // and then the db must be closed
577 mDatabase->Close();
578 }
579
580 // Fire the abort event if there are no outstanding requests. Otherwise the
581 // abort event will be fired when all outstanding requests finish.
582 if (needToCommitOrRollback) {
583 return CommitOrRollback();
584 }
585
586 return NS_OK;
587 }
588
589 nsresult
590 IDBTransaction::Abort(IDBRequest* aRequest)
591 {
592 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
593 NS_ASSERTION(aRequest, "This is undesirable.");
594
595 ErrorResult rv;
596 nsRefPtr<DOMError> error = aRequest->GetError(rv);
597
598 return AbortInternal(aRequest->GetErrorCode(), error.forget());
599 }
600
601 nsresult
602 IDBTransaction::Abort(nsresult aErrorCode)
603 {
604 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
605
606 nsRefPtr<DOMError> error = new DOMError(GetOwner(), aErrorCode);
607 return AbortInternal(aErrorCode, error.forget());
608 }
609
610 NS_IMPL_CYCLE_COLLECTION_CLASS(IDBTransaction)
611
612 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(IDBTransaction,
613 IDBWrapperCache)
614 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDatabase)
615 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mError)
616 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCreatedObjectStores)
617 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDeletedObjectStores)
618 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
619
620 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(IDBTransaction, IDBWrapperCache)
621 // Don't unlink mDatabase!
622 NS_IMPL_CYCLE_COLLECTION_UNLINK(mError)
623 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCreatedObjectStores)
624 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDeletedObjectStores)
625 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
626
627 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(IDBTransaction)
628 NS_INTERFACE_MAP_ENTRY(nsIRunnable)
629 NS_INTERFACE_MAP_END_INHERITING(IDBWrapperCache)
630
631 NS_IMPL_ADDREF_INHERITED(IDBTransaction, IDBWrapperCache)
632 NS_IMPL_RELEASE_INHERITED(IDBTransaction, IDBWrapperCache)
633
634 JSObject*
635 IDBTransaction::WrapObject(JSContext* aCx)
636 {
637 return IDBTransactionBinding::Wrap(aCx, this);
638 }
639
640 mozilla::dom::IDBTransactionMode
641 IDBTransaction::GetMode(ErrorResult& aRv) const
642 {
643 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
644
645 switch (mMode) {
646 case READ_ONLY:
647 return mozilla::dom::IDBTransactionMode::Readonly;
648
649 case READ_WRITE:
650 return mozilla::dom::IDBTransactionMode::Readwrite;
651
652 case VERSION_CHANGE:
653 return mozilla::dom::IDBTransactionMode::Versionchange;
654
655 case MODE_INVALID:
656 default:
657 aRv.Throw(NS_ERROR_UNEXPECTED);
658 return mozilla::dom::IDBTransactionMode::Readonly;
659 }
660 }
661
662 DOMError*
663 IDBTransaction::GetError(ErrorResult& aRv)
664 {
665 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
666
667 if (IsOpen()) {
668 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
669 return nullptr;
670 }
671
672 return mError;
673 }
674
675 already_AddRefed<DOMStringList>
676 IDBTransaction::GetObjectStoreNames(ErrorResult& aRv)
677 {
678 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
679
680 nsRefPtr<DOMStringList> list(new DOMStringList());
681
682 if (mMode == IDBTransaction::VERSION_CHANGE) {
683 mDatabaseInfo->GetObjectStoreNames(list->StringArray());
684 }
685 else {
686 list->StringArray() = mObjectStoreNames;
687 }
688
689 return list.forget();
690 }
691
692 already_AddRefed<IDBObjectStore>
693 IDBTransaction::ObjectStore(const nsAString& aName, ErrorResult& aRv)
694 {
695 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
696
697 if (IsFinished()) {
698 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
699 return nullptr;
700 }
701
702 ObjectStoreInfo* info = nullptr;
703
704 if (mMode == IDBTransaction::VERSION_CHANGE ||
705 mObjectStoreNames.Contains(aName)) {
706 info = mDatabaseInfo->GetObjectStore(aName);
707 }
708
709 if (!info) {
710 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR);
711 return nullptr;
712 }
713
714 nsRefPtr<IDBObjectStore> objectStore =
715 GetOrCreateObjectStore(aName, info, false);
716 if (!objectStore) {
717 IDB_WARNING("Failed to get or create object store!");
718 aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
719 return nullptr;
720 }
721
722 return objectStore.forget();
723 }
724
725 nsresult
726 IDBTransaction::PreHandleEvent(EventChainPreVisitor& aVisitor)
727 {
728 aVisitor.mCanHandle = true;
729 aVisitor.mParentTarget = mDatabase;
730 return NS_OK;
731 }
732
733 NS_IMETHODIMP
734 IDBTransaction::Run()
735 {
736 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
737
738 // We're back at the event loop, no longer newborn.
739 mCreating = false;
740
741 // Maybe set the readyState to DONE if there were no requests generated.
742 if (mReadyState == IDBTransaction::INITIAL) {
743 mReadyState = IDBTransaction::DONE;
744
745 if (NS_FAILED(CommitOrRollback())) {
746 NS_WARNING("Failed to commit!");
747 }
748 }
749
750 return NS_OK;
751 }
752
753 CommitHelper::CommitHelper(
754 IDBTransaction* aTransaction,
755 IDBTransactionListener* aListener,
756 const nsTArray<nsRefPtr<IDBObjectStore> >& aUpdatedObjectStores)
757 : mTransaction(aTransaction),
758 mListener(aListener),
759 mAbortCode(aTransaction->mAbortCode)
760 {
761 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
762
763 mConnection.swap(aTransaction->mConnection);
764 mUpdateFileRefcountFunction.swap(aTransaction->mUpdateFileRefcountFunction);
765
766 for (uint32_t i = 0; i < aUpdatedObjectStores.Length(); i++) {
767 ObjectStoreInfo* info = aUpdatedObjectStores[i]->Info();
768 if (info->comittedAutoIncrementId != info->nextAutoIncrementId) {
769 mAutoIncrementObjectStores.AppendElement(aUpdatedObjectStores[i]);
770 }
771 }
772 }
773
774 CommitHelper::CommitHelper(IDBTransaction* aTransaction,
775 nsresult aAbortCode)
776 : mTransaction(aTransaction),
777 mAbortCode(aAbortCode)
778 {
779 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
780 }
781
782 CommitHelper::~CommitHelper()
783 {
784 }
785
786 NS_IMPL_ISUPPORTS(CommitHelper, nsIRunnable)
787
788 NS_IMETHODIMP
789 CommitHelper::Run()
790 {
791 if (NS_IsMainThread()) {
792 PROFILER_MAIN_THREAD_LABEL("IndexedDB", "CommitHelper::Run");
793
794 NS_ASSERTION(mDoomedObjects.IsEmpty(), "Didn't release doomed objects!");
795
796 mTransaction->mReadyState = IDBTransaction::DONE;
797
798 // Release file infos on the main thread, so they will eventually get
799 // destroyed on correct thread.
800 mTransaction->ClearCreatedFileInfos();
801 if (mUpdateFileRefcountFunction) {
802 mUpdateFileRefcountFunction->ClearFileInfoEntries();
803 mUpdateFileRefcountFunction = nullptr;
804 }
805
806 nsCOMPtr<nsIDOMEvent> event;
807 if (NS_FAILED(mAbortCode)) {
808 if (mTransaction->GetMode() == IDBTransaction::VERSION_CHANGE) {
809 // This will make the database take a snapshot of it's DatabaseInfo
810 mTransaction->Database()->Close();
811 // Then remove the info from the hash as it contains invalid data.
812 DatabaseInfo::Remove(mTransaction->Database()->Id());
813 }
814
815 event = CreateGenericEvent(mTransaction,
816 NS_LITERAL_STRING(ABORT_EVT_STR),
817 eDoesBubble, eNotCancelable);
818
819 // The transaction may already have an error object (e.g. if one of the
820 // requests failed). If it doesn't, and it wasn't aborted
821 // programmatically, create one now.
822 if (!mTransaction->mError &&
823 mAbortCode != NS_ERROR_DOM_INDEXEDDB_ABORT_ERR) {
824 mTransaction->mError = new DOMError(mTransaction->GetOwner(), mAbortCode);
825 }
826 }
827 else {
828 event = CreateGenericEvent(mTransaction,
829 NS_LITERAL_STRING(COMPLETE_EVT_STR),
830 eDoesNotBubble, eNotCancelable);
831 }
832 IDB_ENSURE_TRUE(event, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
833
834 if (mListener) {
835 mListener->NotifyTransactionPreComplete(mTransaction);
836 }
837
838 IDB_PROFILER_MARK("IndexedDB Transaction %llu: Complete (rv = %lu)",
839 "IDBTransaction[%llu] MT Complete",
840 mTransaction->GetSerialNumber(), mAbortCode);
841
842 bool dummy;
843 if (NS_FAILED(mTransaction->DispatchEvent(event, &dummy))) {
844 NS_WARNING("Dispatch failed!");
845 }
846
847 #ifdef DEBUG
848 mTransaction->mFiredCompleteOrAbort = true;
849 #endif
850
851 if (mListener) {
852 mListener->NotifyTransactionPostComplete(mTransaction);
853 }
854
855 mTransaction = nullptr;
856
857 return NS_OK;
858 }
859
860 PROFILER_LABEL("IndexedDB", "CommitHelper::Run");
861
862 IDBDatabase* database = mTransaction->Database();
863 if (database->IsInvalidated()) {
864 IDB_REPORT_INTERNAL_ERR();
865 mAbortCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
866 }
867
868 if (mConnection) {
869 QuotaManager::SetCurrentWindow(database->GetOwner());
870
871 if (NS_SUCCEEDED(mAbortCode) && mUpdateFileRefcountFunction &&
872 NS_FAILED(mUpdateFileRefcountFunction->WillCommit(mConnection))) {
873 IDB_REPORT_INTERNAL_ERR();
874 mAbortCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
875 }
876
877 if (NS_SUCCEEDED(mAbortCode) && NS_FAILED(WriteAutoIncrementCounts())) {
878 IDB_REPORT_INTERNAL_ERR();
879 mAbortCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
880 }
881
882 if (NS_SUCCEEDED(mAbortCode)) {
883 NS_NAMED_LITERAL_CSTRING(release, "COMMIT TRANSACTION");
884 nsresult rv = mConnection->ExecuteSimpleSQL(release);
885 if (NS_SUCCEEDED(rv)) {
886 if (mUpdateFileRefcountFunction) {
887 mUpdateFileRefcountFunction->DidCommit();
888 }
889 CommitAutoIncrementCounts();
890 }
891 else if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) {
892 // mozstorage translates SQLITE_FULL to NS_ERROR_FILE_NO_DEVICE_SPACE,
893 // which we know better as NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR.
894 mAbortCode = NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
895 }
896 else {
897 IDB_REPORT_INTERNAL_ERR();
898 mAbortCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
899 }
900 }
901
902 if (NS_FAILED(mAbortCode)) {
903 if (mUpdateFileRefcountFunction) {
904 mUpdateFileRefcountFunction->DidAbort();
905 }
906 RevertAutoIncrementCounts();
907 NS_NAMED_LITERAL_CSTRING(rollback, "ROLLBACK TRANSACTION");
908 if (NS_FAILED(mConnection->ExecuteSimpleSQL(rollback))) {
909 NS_WARNING("Failed to rollback transaction!");
910 }
911 }
912 }
913
914 mDoomedObjects.Clear();
915
916 if (mConnection) {
917 if (mUpdateFileRefcountFunction) {
918 nsresult rv = mConnection->RemoveFunction(
919 NS_LITERAL_CSTRING("update_refcount"));
920 if (NS_FAILED(rv)) {
921 NS_WARNING("Failed to remove function!");
922 }
923 }
924
925 mConnection->Close();
926 mConnection = nullptr;
927
928 QuotaManager::SetCurrentWindow(nullptr);
929 }
930
931 return NS_OK;
932 }
933
934 nsresult
935 CommitHelper::WriteAutoIncrementCounts()
936 {
937 nsCOMPtr<mozIStorageStatement> stmt;
938 nsresult rv;
939 for (uint32_t i = 0; i < mAutoIncrementObjectStores.Length(); i++) {
940 ObjectStoreInfo* info = mAutoIncrementObjectStores[i]->Info();
941 if (!stmt) {
942 rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(
943 "UPDATE object_store SET auto_increment = :ai "
944 "WHERE id = :osid;"), getter_AddRefs(stmt));
945 NS_ENSURE_SUCCESS(rv, rv);
946 }
947 else {
948 stmt->Reset();
949 }
950
951 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), info->id);
952 NS_ENSURE_SUCCESS(rv, rv);
953
954 rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("ai"),
955 info->nextAutoIncrementId);
956 NS_ENSURE_SUCCESS(rv, rv);
957
958 rv = stmt->Execute();
959 NS_ENSURE_SUCCESS(rv, rv);
960 }
961
962 return NS_OK;
963 }
964
965 void
966 CommitHelper::CommitAutoIncrementCounts()
967 {
968 for (uint32_t i = 0; i < mAutoIncrementObjectStores.Length(); i++) {
969 ObjectStoreInfo* info = mAutoIncrementObjectStores[i]->Info();
970 info->comittedAutoIncrementId = info->nextAutoIncrementId;
971 }
972 }
973
974 void
975 CommitHelper::RevertAutoIncrementCounts()
976 {
977 for (uint32_t i = 0; i < mAutoIncrementObjectStores.Length(); i++) {
978 ObjectStoreInfo* info = mAutoIncrementObjectStores[i]->Info();
979 info->nextAutoIncrementId = info->comittedAutoIncrementId;
980 }
981 }
982
983 NS_IMPL_ISUPPORTS(UpdateRefcountFunction, mozIStorageFunction)
984
985 NS_IMETHODIMP
986 UpdateRefcountFunction::OnFunctionCall(mozIStorageValueArray* aValues,
987 nsIVariant** _retval)
988 {
989 *_retval = nullptr;
990
991 uint32_t numEntries;
992 nsresult rv = aValues->GetNumEntries(&numEntries);
993 NS_ENSURE_SUCCESS(rv, rv);
994 NS_ASSERTION(numEntries == 2, "unexpected number of arguments");
995
996 #ifdef DEBUG
997 int32_t type1 = mozIStorageValueArray::VALUE_TYPE_NULL;
998 aValues->GetTypeOfIndex(0, &type1);
999
1000 int32_t type2 = mozIStorageValueArray::VALUE_TYPE_NULL;
1001 aValues->GetTypeOfIndex(1, &type2);
1002
1003 NS_ASSERTION(!(type1 == mozIStorageValueArray::VALUE_TYPE_NULL &&
1004 type2 == mozIStorageValueArray::VALUE_TYPE_NULL),
1005 "Shouldn't be called!");
1006 #endif
1007
1008 rv = ProcessValue(aValues, 0, eDecrement);
1009 NS_ENSURE_SUCCESS(rv, rv);
1010
1011 rv = ProcessValue(aValues, 1, eIncrement);
1012 NS_ENSURE_SUCCESS(rv, rv);
1013
1014 return NS_OK;
1015 }
1016
1017 nsresult
1018 UpdateRefcountFunction::WillCommit(mozIStorageConnection* aConnection)
1019 {
1020 DatabaseUpdateFunction function(aConnection, this);
1021
1022 mFileInfoEntries.EnumerateRead(DatabaseUpdateCallback, &function);
1023
1024 nsresult rv = function.ErrorCode();
1025 NS_ENSURE_SUCCESS(rv, rv);
1026
1027 rv = CreateJournals();
1028 NS_ENSURE_SUCCESS(rv, rv);
1029
1030 return NS_OK;
1031 }
1032
1033 void
1034 UpdateRefcountFunction::DidCommit()
1035 {
1036 mFileInfoEntries.EnumerateRead(FileInfoUpdateCallback, nullptr);
1037
1038 nsresult rv = RemoveJournals(mJournalsToRemoveAfterCommit);
1039 NS_ENSURE_SUCCESS_VOID(rv);
1040 }
1041
1042 void
1043 UpdateRefcountFunction::DidAbort()
1044 {
1045 nsresult rv = RemoveJournals(mJournalsToRemoveAfterAbort);
1046 NS_ENSURE_SUCCESS_VOID(rv);
1047 }
1048
1049 nsresult
1050 UpdateRefcountFunction::ProcessValue(mozIStorageValueArray* aValues,
1051 int32_t aIndex,
1052 UpdateType aUpdateType)
1053 {
1054 int32_t type;
1055 aValues->GetTypeOfIndex(aIndex, &type);
1056 if (type == mozIStorageValueArray::VALUE_TYPE_NULL) {
1057 return NS_OK;
1058 }
1059
1060 nsString ids;
1061 aValues->GetString(aIndex, ids);
1062
1063 nsTArray<int64_t> fileIds;
1064 nsresult rv = IDBObjectStore::ConvertFileIdsToArray(ids, fileIds);
1065 NS_ENSURE_SUCCESS(rv, rv);
1066
1067 for (uint32_t i = 0; i < fileIds.Length(); i++) {
1068 int64_t id = fileIds.ElementAt(i);
1069
1070 FileInfoEntry* entry;
1071 if (!mFileInfoEntries.Get(id, &entry)) {
1072 nsRefPtr<FileInfo> fileInfo = mFileManager->GetFileInfo(id);
1073 NS_ASSERTION(fileInfo, "Shouldn't be null!");
1074
1075 nsAutoPtr<FileInfoEntry> newEntry(new FileInfoEntry(fileInfo));
1076 mFileInfoEntries.Put(id, newEntry);
1077 entry = newEntry.forget();
1078 }
1079
1080 if (mInSavepoint) {
1081 mSavepointEntriesIndex.Put(id, entry);
1082 }
1083
1084 switch (aUpdateType) {
1085 case eIncrement:
1086 entry->mDelta++;
1087 if (mInSavepoint) {
1088 entry->mSavepointDelta++;
1089 }
1090 break;
1091 case eDecrement:
1092 entry->mDelta--;
1093 if (mInSavepoint) {
1094 entry->mSavepointDelta--;
1095 }
1096 break;
1097 default:
1098 NS_NOTREACHED("Unknown update type!");
1099 }
1100 }
1101
1102 return NS_OK;
1103 }
1104
1105 nsresult
1106 UpdateRefcountFunction::CreateJournals()
1107 {
1108 nsCOMPtr<nsIFile> journalDirectory = mFileManager->GetJournalDirectory();
1109 NS_ENSURE_TRUE(journalDirectory, NS_ERROR_FAILURE);
1110
1111 for (uint32_t i = 0; i < mJournalsToCreateBeforeCommit.Length(); i++) {
1112 int64_t id = mJournalsToCreateBeforeCommit[i];
1113
1114 nsCOMPtr<nsIFile> file =
1115 mFileManager->GetFileForId(journalDirectory, id);
1116 NS_ENSURE_TRUE(file, NS_ERROR_FAILURE);
1117
1118 nsresult rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
1119 NS_ENSURE_SUCCESS(rv, rv);
1120
1121 mJournalsToRemoveAfterAbort.AppendElement(id);
1122 }
1123
1124 return NS_OK;
1125 }
1126
1127 nsresult
1128 UpdateRefcountFunction::RemoveJournals(const nsTArray<int64_t>& aJournals)
1129 {
1130 nsCOMPtr<nsIFile> journalDirectory = mFileManager->GetJournalDirectory();
1131 NS_ENSURE_TRUE(journalDirectory, NS_ERROR_FAILURE);
1132
1133 for (uint32_t index = 0; index < aJournals.Length(); index++) {
1134 nsCOMPtr<nsIFile> file =
1135 mFileManager->GetFileForId(journalDirectory, aJournals[index]);
1136 NS_ENSURE_TRUE(file, NS_ERROR_FAILURE);
1137
1138 if (NS_FAILED(file->Remove(false))) {
1139 NS_WARNING("Failed to removed journal!");
1140 }
1141 }
1142
1143 return NS_OK;
1144 }
1145
1146 PLDHashOperator
1147 UpdateRefcountFunction::DatabaseUpdateCallback(const uint64_t& aKey,
1148 FileInfoEntry* aValue,
1149 void* aUserArg)
1150 {
1151 if (!aValue->mDelta) {
1152 return PL_DHASH_NEXT;
1153 }
1154
1155 DatabaseUpdateFunction* function =
1156 static_cast<DatabaseUpdateFunction*>(aUserArg);
1157
1158 if (!function->Update(aKey, aValue->mDelta)) {
1159 return PL_DHASH_STOP;
1160 }
1161
1162 return PL_DHASH_NEXT;
1163 }
1164
1165 PLDHashOperator
1166 UpdateRefcountFunction::FileInfoUpdateCallback(const uint64_t& aKey,
1167 FileInfoEntry* aValue,
1168 void* aUserArg)
1169 {
1170 if (aValue->mDelta) {
1171 aValue->mFileInfo->UpdateDBRefs(aValue->mDelta);
1172 }
1173
1174 return PL_DHASH_NEXT;
1175 }
1176
1177 PLDHashOperator
1178 UpdateRefcountFunction::RollbackSavepointCallback(const uint64_t& aKey,
1179 FileInfoEntry* aValue,
1180 void* aUserArg)
1181 {
1182 aValue->mDelta -= aValue->mSavepointDelta;
1183
1184 return PL_DHASH_NEXT;
1185 }
1186
1187 bool
1188 UpdateRefcountFunction::DatabaseUpdateFunction::Update(int64_t aId,
1189 int32_t aDelta)
1190 {
1191 nsresult rv = UpdateInternal(aId, aDelta);
1192 if (NS_FAILED(rv)) {
1193 mErrorCode = rv;
1194 return false;
1195 }
1196
1197 return true;
1198 }
1199
1200 nsresult
1201 UpdateRefcountFunction::DatabaseUpdateFunction::UpdateInternal(int64_t aId,
1202 int32_t aDelta)
1203 {
1204 nsresult rv;
1205
1206 if (!mUpdateStatement) {
1207 rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(
1208 "UPDATE file SET refcount = refcount + :delta WHERE id = :id"
1209 ), getter_AddRefs(mUpdateStatement));
1210 NS_ENSURE_SUCCESS(rv, rv);
1211 }
1212
1213 mozStorageStatementScoper updateScoper(mUpdateStatement);
1214
1215 rv = mUpdateStatement->BindInt32ByName(NS_LITERAL_CSTRING("delta"), aDelta);
1216 NS_ENSURE_SUCCESS(rv, rv);
1217
1218 rv = mUpdateStatement->BindInt64ByName(NS_LITERAL_CSTRING("id"), aId);
1219 NS_ENSURE_SUCCESS(rv, rv);
1220
1221 rv = mUpdateStatement->Execute();
1222 NS_ENSURE_SUCCESS(rv, rv);
1223
1224 int32_t rows;
1225 rv = mConnection->GetAffectedRows(&rows);
1226 NS_ENSURE_SUCCESS(rv, rv);
1227
1228 if (rows > 0) {
1229 if (!mSelectStatement) {
1230 rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(
1231 "SELECT id FROM file where id = :id"
1232 ), getter_AddRefs(mSelectStatement));
1233 NS_ENSURE_SUCCESS(rv, rv);
1234 }
1235
1236 mozStorageStatementScoper selectScoper(mSelectStatement);
1237
1238 rv = mSelectStatement->BindInt64ByName(NS_LITERAL_CSTRING("id"), aId);
1239 NS_ENSURE_SUCCESS(rv, rv);
1240
1241 bool hasResult;
1242 rv = mSelectStatement->ExecuteStep(&hasResult);
1243 NS_ENSURE_SUCCESS(rv, rv);
1244
1245 if (!hasResult) {
1246 // Don't have to create the journal here, we can create all at once,
1247 // just before commit
1248 mFunction->mJournalsToCreateBeforeCommit.AppendElement(aId);
1249 }
1250
1251 return NS_OK;
1252 }
1253
1254 if (!mInsertStatement) {
1255 rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(
1256 "INSERT INTO file (id, refcount) VALUES(:id, :delta)"
1257 ), getter_AddRefs(mInsertStatement));
1258 NS_ENSURE_SUCCESS(rv, rv);
1259 }
1260
1261 mozStorageStatementScoper insertScoper(mInsertStatement);
1262
1263 rv = mInsertStatement->BindInt64ByName(NS_LITERAL_CSTRING("id"), aId);
1264 NS_ENSURE_SUCCESS(rv, rv);
1265
1266 rv = mInsertStatement->BindInt32ByName(NS_LITERAL_CSTRING("delta"), aDelta);
1267 NS_ENSURE_SUCCESS(rv, rv);
1268
1269 rv = mInsertStatement->Execute();
1270 NS_ENSURE_SUCCESS(rv, rv);
1271
1272 mFunction->mJournalsToRemoveAfterCommit.AppendElement(aId);
1273
1274 return NS_OK;
1275 }

mercurial