Bug 618140 - 'IndexedDB: Don't fire success event callbacks once a transaction has been aborted'. r=sicking, a=blocking.
authorBen Turner <bent.mozilla@gmail.com>
Wed, 15 Dec 2010 13:21:17 -0800
changeset 59271 a1f2e2bb9a7a0a94236d8dc345232197bddb672c
parent 59270 6e7a39f7acb44c5a6c1fe70e11a8ad4fa8feb8fd
child 59272 0e45f2a9fccaad2cf9eaabf92575ba0fea021e9d
push id1
push usershaver@mozilla.com
push dateTue, 04 Jan 2011 17:58:04 +0000
reviewerssicking, blocking
bugs618140
milestone2.0b9pre
Bug 618140 - 'IndexedDB: Don't fire success event callbacks once a transaction has been aborted'. r=sicking, a=blocking.
dom/indexedDB/AsyncConnectionHelper.cpp
dom/indexedDB/IDBCursor.cpp
dom/indexedDB/IDBIndex.cpp
dom/indexedDB/IDBObjectStore.cpp
dom/indexedDB/IDBObjectStore.h
dom/indexedDB/IDBTransaction.cpp
dom/indexedDB/IDBTransaction.h
dom/indexedDB/test/Makefile.in
dom/indexedDB/test/test_success_events_after_abort.html
dom/indexedDB/test/test_transaction_abort.html
--- a/dom/indexedDB/AsyncConnectionHelper.cpp
+++ b/dom/indexedDB/AsyncConnectionHelper.cpp
@@ -140,16 +140,24 @@ NS_IMPL_THREADSAFE_ISUPPORTS2(AsyncConne
 NS_IMETHODIMP
 AsyncConnectionHelper::Run()
 {
   if (NS_IsMainThread()) {
     if (mRequest) {
       mRequest->SetDone();
     }
 
+    if (mTransaction &&
+        mTransaction->IsAborted() &&
+        NS_SUCCEEDED(mResultCode)) {
+      // Don't fire success events if the transaction has since been aborted.
+      // Instead convert to an error event.
+      mResultCode = NS_ERROR_DOM_INDEXEDDB_ABORT_ERR;
+    }
+
     IDBTransaction* oldTransaction = gCurrentTransaction;
     gCurrentTransaction = mTransaction;
 
     // Call OnError if the database had an error or if the OnSuccess handler
     // has an error.
     if (NS_FAILED(mResultCode) ||
         NS_FAILED((mResultCode = OnSuccess(mRequest)))) {
       OnError(mRequest, mResultCode);
@@ -370,19 +378,24 @@ AsyncConnectionHelper::OnSuccess(nsIDOME
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   nsCOMPtr<nsIPrivateDOMEvent> privateEvent = do_QueryInterface(event);
   NS_ASSERTION(privateEvent, "This should always QI properly!");
 
   nsEvent* internalEvent = privateEvent->GetInternalNSEvent();
   NS_ASSERTION(internalEvent, "This should never be null!");
 
+  NS_ASSERTION(!mTransaction ||
+               mTransaction->IsOpen() ||
+               mTransaction->IsAborted(),
+               "How else can this be closed?!");
+
   if ((internalEvent->flags & NS_EVENT_FLAG_EXCEPTION_THROWN) &&
       mTransaction &&
-      mTransaction->TransactionIsOpen()) {
+      mTransaction->IsOpen()) {
     rv = mTransaction->Abort();
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 
 void
@@ -396,19 +409,24 @@ AsyncConnectionHelper::OnError(nsIDOMEve
   if (!event) {
     NS_ERROR("Failed to create event!");
     return;
   }
 
   PRBool doDefault;
   nsresult rv = aTarget->DispatchEvent(event, &doDefault);
   if (NS_SUCCEEDED(rv)) {
+    NS_ASSERTION(!mTransaction ||
+                 mTransaction->IsOpen() ||
+                 mTransaction->IsAborted(),
+                 "How else can this be closed?!");
+
     if (doDefault &&
         mTransaction &&
-        mTransaction->TransactionIsOpen() &&
+        mTransaction->IsOpen() &&
         NS_FAILED(mTransaction->Abort())) {
       NS_WARNING("Failed to abort transaction!");
     }
   }
   else {
     NS_WARNING("DispatchEvent failed!");
   }
 }
--- a/dom/indexedDB/IDBCursor.cpp
+++ b/dom/indexedDB/IDBCursor.cpp
@@ -430,17 +430,17 @@ IDBCursor::GetValue(JSContext* aCx,
 }
 
 NS_IMETHODIMP
 IDBCursor::Continue(const jsval &aKey,
                     JSContext* aCx)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  if (!mTransaction->TransactionIsOpen()) {
+  if (!mTransaction->IsOpen()) {
     return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR;
   }
 
   if (mContinueCalled) {
     return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
   }
 
   Key key;
@@ -509,17 +509,17 @@ IDBCursor::Continue(const jsval &aKey,
 
 NS_IMETHODIMP
 IDBCursor::Update(const jsval& aValue,
                   JSContext* aCx,
                   nsIIDBRequest** _retval)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  if (!mTransaction->TransactionIsOpen()) {
+  if (!mTransaction->IsOpen()) {
     return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR;
   }
 
   if (!mTransaction->IsWriteAllowed()) {
     return NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR;
   }
 
   if (mType == INDEXKEY) {
@@ -572,17 +572,17 @@ IDBCursor::Update(const jsval& aValue,
 }
 
 NS_IMETHODIMP
 IDBCursor::Delete(JSContext* aCx,
                   nsIIDBRequest** _retval)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  if (!mTransaction->TransactionIsOpen()) {
+  if (!mTransaction->IsOpen()) {
     return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR;
   }
 
   if (!mTransaction->IsWriteAllowed()) {
     return NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR;
   }
 
   if (mType == INDEXKEY) {
--- a/dom/indexedDB/IDBIndex.cpp
+++ b/dom/indexedDB/IDBIndex.cpp
@@ -348,17 +348,17 @@ IDBIndex::GetObjectStore(nsIIDBObjectSto
 
 NS_IMETHODIMP
 IDBIndex::Get(nsIVariant* aKey,
               nsIIDBRequest** _retval)
 {
   NS_PRECONDITION(NS_IsMainThread(), "Wrong thread!");
 
   IDBTransaction* transaction = mObjectStore->Transaction();
-  if (!transaction->TransactionIsOpen()) {
+  if (!transaction->IsOpen()) {
     return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR;
   }
 
   Key key;
   nsresult rv = IDBObjectStore::GetKeyFromVariant(aKey, key);
   if (NS_FAILED(rv)) {
     return rv;
   }
@@ -380,17 +380,17 @@ IDBIndex::Get(nsIVariant* aKey,
 
 NS_IMETHODIMP
 IDBIndex::GetKey(nsIVariant* aKey,
                  nsIIDBRequest** _retval)
 {
   NS_PRECONDITION(NS_IsMainThread(), "Wrong thread!");
 
   IDBTransaction* transaction = mObjectStore->Transaction();
-  if (!transaction->TransactionIsOpen()) {
+  if (!transaction->IsOpen()) {
     return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR;
   }
 
   Key key;
   nsresult rv = IDBObjectStore::GetKeyFromVariant(aKey, key);
   if (NS_FAILED(rv)) {
     return rv;
   }
@@ -416,17 +416,17 @@ NS_IMETHODIMP
 IDBIndex::GetAll(nsIVariant* aKey,
                  PRUint32 aLimit,
                  PRUint8 aOptionalArgCount,
                  nsIIDBRequest** _retval)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   IDBTransaction* transaction = mObjectStore->Transaction();
-  if (!transaction->TransactionIsOpen()) {
+  if (!transaction->IsOpen()) {
     return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR;
   }
 
   nsresult rv;
 
   Key key;
   if (aOptionalArgCount &&
       NS_FAILED(IDBObjectStore::GetKeyFromVariant(aKey, key))) {
@@ -460,17 +460,17 @@ NS_IMETHODIMP
 IDBIndex::GetAllKeys(nsIVariant* aKey,
                      PRUint32 aLimit,
                      PRUint8 aOptionalArgCount,
                      nsIIDBRequest** _retval)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   IDBTransaction* transaction = mObjectStore->Transaction();
-  if (!transaction->TransactionIsOpen()) {
+  if (!transaction->IsOpen()) {
     return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR;
   }
 
   nsresult rv;
 
   Key key;
   if (aOptionalArgCount &&
       NS_FAILED(IDBObjectStore::GetKeyFromVariant(aKey, key))) {
@@ -504,17 +504,17 @@ NS_IMETHODIMP
 IDBIndex::OpenCursor(nsIIDBKeyRange* aKeyRange,
                      PRUint16 aDirection,
                      PRUint8 aOptionalArgCount,
                      nsIIDBRequest** _retval)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   IDBTransaction* transaction = mObjectStore->Transaction();
-  if (!transaction->TransactionIsOpen()) {
+  if (!transaction->IsOpen()) {
     return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR;
   }
 
   nsresult rv;
   Key lowerKey, upperKey;
   PRBool lowerOpen = PR_FALSE, upperOpen = PR_FALSE;
 
   if (aKeyRange) {
@@ -572,17 +572,17 @@ NS_IMETHODIMP
 IDBIndex::OpenKeyCursor(nsIIDBKeyRange* aKeyRange,
                         PRUint16 aDirection,
                         PRUint8 aOptionalArgCount,
                         nsIIDBRequest** _retval)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   IDBTransaction* transaction = mObjectStore->Transaction();
-  if (!transaction->TransactionIsOpen()) {
+  if (!transaction->IsOpen()) {
     return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR;
   }
 
   nsresult rv;
   Key lowerKey, upperKey;
   PRBool lowerOpen = PR_FALSE, upperOpen = PR_FALSE;
 
   if (aKeyRange) {
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -795,17 +795,17 @@ IDBObjectStore::AddOrPut(const jsval& aV
                          const jsval& aKey,
                          JSContext* aCx,
                          PRUint8 aOptionalArgCount,
                          nsIIDBRequest** _retval,
                          bool aOverwrite)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  if (!mTransaction->TransactionIsOpen()) {
+  if (!mTransaction->IsOpen()) {
     return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR;
   }
 
   if (!IsWriteAllowed()) {
     return NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR;
   }
 
   jsval keyval = (aOptionalArgCount >= 1) ? aKey : JSVAL_VOID;
@@ -922,17 +922,17 @@ IDBObjectStore::GetIndexNames(nsIDOMDOMS
 }
 
 NS_IMETHODIMP
 IDBObjectStore::Get(nsIVariant* aKey,
                     nsIIDBRequest** _retval)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  if (!mTransaction->TransactionIsOpen()) {
+  if (!mTransaction->IsOpen()) {
     return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR;
   }
 
   Key key;
   nsresult rv = GetKeyFromVariant(aKey, key);
   if (NS_FAILED(rv)) {
     return rv;
   }
@@ -956,17 +956,17 @@ IDBObjectStore::Get(nsIVariant* aKey,
 NS_IMETHODIMP
 IDBObjectStore::GetAll(nsIIDBKeyRange* aKeyRange,
                        PRUint32 aLimit,
                        PRUint8 aOptionalArgCount,
                        nsIIDBRequest** _retval)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  if (!mTransaction->TransactionIsOpen()) {
+  if (!mTransaction->IsOpen()) {
     return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR;
   }
 
   if (aOptionalArgCount < 2) {
     aLimit = PR_UINT32_MAX;
   }
 
   nsresult rv;
@@ -1038,17 +1038,17 @@ IDBObjectStore::Put(const jsval& aValue,
 
 NS_IMETHODIMP
 IDBObjectStore::Delete(const jsval& aKey,
                        JSContext* aCx,
                        nsIIDBRequest** _retval)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  if (!mTransaction->TransactionIsOpen()) {
+  if (!mTransaction->IsOpen()) {
     return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR;
   }
 
   if (!IsWriteAllowed()) {
     return NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR;
   }
 
   Key key;
@@ -1074,17 +1074,17 @@ IDBObjectStore::Delete(const jsval& aKey
   return NS_OK;
 }
 
 NS_IMETHODIMP
 IDBObjectStore::Clear(nsIIDBRequest** _retval)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  if (!mTransaction->TransactionIsOpen()) {
+  if (!mTransaction->IsOpen()) {
     return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR;
   }
 
   if (!IsWriteAllowed()) {
     return NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR;
   }
 
   nsRefPtr<IDBRequest> request = GenerateRequest(this);
@@ -1102,17 +1102,17 @@ IDBObjectStore::Clear(nsIIDBRequest** _r
 NS_IMETHODIMP
 IDBObjectStore::OpenCursor(nsIIDBKeyRange* aKeyRange,
                            PRUint16 aDirection,
                            PRUint8 aOptionalArgCount,
                            nsIIDBRequest** _retval)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  if (!mTransaction->TransactionIsOpen()) {
+  if (!mTransaction->IsOpen()) {
     return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR;
   }
 
   nsresult rv;
   Key lowerKey, upperKey;
   PRBool lowerOpen = PR_FALSE, upperOpen = PR_FALSE;
 
   if (aKeyRange) {
@@ -1199,17 +1199,17 @@ IDBObjectStore::CreateIndex(const nsAStr
       break;
     }
   }
 
   if (found) {
     return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
   }
 
-  NS_ASSERTION(mTransaction->TransactionIsOpen(), "Impossible!");
+  NS_ASSERTION(mTransaction->IsOpen(), "Impossible!");
 
   DatabaseInfo* databaseInfo;
   if (!DatabaseInfo::Get(mTransaction->Database()->Id(), &databaseInfo)) {
     NS_ERROR("This should never fail!");
   }
 
   IndexInfo* indexInfo = info->indexes.AppendElement();
   if (!indexInfo) {
@@ -1254,17 +1254,17 @@ IDBObjectStore::CreateIndex(const nsAStr
 }
 
 NS_IMETHODIMP
 IDBObjectStore::Index(const nsAString& aName,
                       nsIIDBIndex** _retval)
 {
   NS_PRECONDITION(NS_IsMainThread(), "Wrong thread!");
 
-  if (!mTransaction->TransactionIsOpen()) {
+  if (!mTransaction->IsOpen()) {
     return NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR;
   }
 
   if (aName.IsEmpty()) {
     return NS_ERROR_DOM_INDEXEDDB_NON_TRANSIENT_ERR;
   }
 
   ObjectStoreInfo* info;
@@ -1320,17 +1320,17 @@ IDBObjectStore::DeleteIndex(const nsAStr
   IDBTransaction* transaction = AsyncConnectionHelper::GetCurrentTransaction();
 
   if (!transaction ||
       transaction != mTransaction ||
       mTransaction->Mode() != nsIIDBTransaction::VERSION_CHANGE) {
     return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
   }
 
-  NS_ASSERTION(mTransaction->TransactionIsOpen(), "Impossible!");
+  NS_ASSERTION(mTransaction->IsOpen(), "Impossible!");
 
   ObjectStoreInfo* info;
   if (!ObjectStoreInfo::Get(mTransaction->Database()->Id(), mName, &info)) {
     NS_ERROR("This should never fail!");
   }
 
   PRUint32 index = 0;
   for (; index < info->indexes.Length(); index++) {
--- a/dom/indexedDB/IDBObjectStore.h
+++ b/dom/indexedDB/IDBObjectStore.h
@@ -106,21 +106,16 @@ public:
                 PRInt64 aObjectDataId,
                 const nsTArray<IndexUpdateInfo>& aUpdateInfoArray);
 
   const nsString& Name() const
   {
     return mName;
   }
 
-  bool TransactionIsOpen() const
-  {
-    return mTransaction->TransactionIsOpen();
-  }
-
   bool IsAutoIncrement() const
   {
     return mAutoIncrement;
   }
 
   bool IsWriteAllowed() const
   {
     return mTransaction->IsWriteAllowed();
--- a/dom/indexedDB/IDBTransaction.cpp
+++ b/dom/indexedDB/IDBTransaction.cpp
@@ -129,27 +129,31 @@ IDBTransaction::IDBTransaction()
 : mReadyState(nsIIDBTransaction::INITIAL),
   mMode(nsIIDBTransaction::READ_ONLY),
   mTimeout(0),
   mPendingRequests(0),
   mCreatedRecursionDepth(0),
   mSavepointCount(0),
   mAborted(false),
   mCreating(false)
+#ifdef DEBUG
+  , mFiredCompleteOrAbort(false)
+#endif
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 }
 
 IDBTransaction::~IDBTransaction()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(!mPendingRequests, "Should have no pending requests here!");
   NS_ASSERTION(!mSavepointCount, "Should have released them all!");
   NS_ASSERTION(!mConnection, "Should have called CommitOrRollback!");
   NS_ASSERTION(!mCreating, "Should have been cleared already!");
+  NS_ASSERTION(mFiredCompleteOrAbort, "Should have fired event!");
 
   if (mListenerManager) {
     mListenerManager->Disconnect();
   }
 }
 
 void
 IDBTransaction::OnNewRequest()
@@ -177,20 +181,16 @@ IDBTransaction::OnRequestFinished()
   }
 }
 
 nsresult
 IDBTransaction::CommitOrRollback()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  if (!mAborted) {
-    NS_ASSERTION(mReadyState == nsIIDBTransaction::LOADING, "Bad state!");
-  }
-
   TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate();
   NS_ENSURE_STATE(pool);
 
   nsRefPtr<CommitHelper> helper(new CommitHelper(this));
 
   mCachedStatements.Enumerate(DoomCachedStatements, helper);
   NS_ASSERTION(!mCachedStatements.Count(), "Statements left!");
 
@@ -548,17 +548,17 @@ IDBTransaction::GetCachedStatement(const
       NS_ERROR("Out of memory?!");
     }
   }
 
   return stmt.forget();
 }
 
 bool
-IDBTransaction::TransactionIsOpen() const
+IDBTransaction::IsOpen() const
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   // If we haven't started anything then we're open.
   if (mReadyState == nsIIDBTransaction::INITIAL) {
     NS_ASSERTION(AsyncConnectionHelper::GetCurrentTransaction() != this,
                  "This should be some other transaction (or null)!");
     return true;
@@ -718,17 +718,17 @@ IDBTransaction::GetObjectStoreNames(nsID
 }
 
 NS_IMETHODIMP
 IDBTransaction::ObjectStore(const nsAString& aName,
                             nsIIDBObjectStore** _retval)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  if (!TransactionIsOpen()) {
+  if (!IsOpen()) {
     return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
   }
 
   ObjectStoreInfo* info = nsnull;
 
   if (mMode == nsIIDBTransaction::VERSION_CHANGE ||
       mObjectStoreNames.Contains(aName)) {
     ObjectStoreInfo::Get(mDatabase->Id(), aName, &info);
@@ -745,22 +745,31 @@ IDBTransaction::ObjectStore(const nsAStr
   return NS_OK;
 }
 
 NS_IMETHODIMP
 IDBTransaction::Abort()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  if (!TransactionIsOpen()) {
+  if (!IsOpen()) {
     return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
   }
 
+  bool needToCommitOrRollback = mReadyState == nsIIDBTransaction::INITIAL;
+
   mAborted = true;
   mReadyState = nsIIDBTransaction::DONE;
+
+  // Fire the abort event if there are no outstanding requests. Otherwise the
+  // abort event will be fired when all outdtanding requests finish.
+  if (needToCommitOrRollback) {
+    return CommitOrRollback();
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 IDBTransaction::SetOnerror(nsIDOMEventListener* aErrorListener)
 {
   return RemoveAddEventListener(NS_LITERAL_STRING(ERROR_EVT_STR),
                                 mOnErrorListener, aErrorListener);
@@ -863,16 +872,20 @@ IDBTransaction::AfterProcessNextEvent(ns
 
   if (aRecursionDepth == mCreatedRecursionDepth) {
     // We're back at the event loop, no longer newborn.
     mCreating = false;
 
     // Maybe set the readyState to DONE if there were no requests generated.
     if (mReadyState == nsIIDBTransaction::INITIAL) {
       mReadyState = nsIIDBTransaction::DONE;
+
+      if (NS_FAILED(CommitOrRollback())) {
+        NS_WARNING("Failed to commit!");
+      }
     }
 
     // No longer need to observe thread events.
     nsCOMPtr<nsIThreadInternal2> thread = do_QueryInterface(aThread);
     NS_ASSERTION(thread, "This must never fail!");
 
     if(NS_FAILED(thread->RemoveObserver(this))) {
       NS_ERROR("Failed to remove observer!");
@@ -931,59 +944,62 @@ CommitHelper::Run()
     }
     NS_ENSURE_TRUE(event, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
     PRBool dummy;
     if (NS_FAILED(mTransaction->DispatchEvent(event, &dummy))) {
       NS_WARNING("Dispatch failed!");
     }
 
+#ifdef DEBUG
+    mTransaction->mFiredCompleteOrAbort = true;
+#endif
     mTransaction = nsnull;
     return NS_OK;
   }
 
-  NS_ASSERTION(mConnection, "This had better not be null!");
-
   IDBDatabase* database = mTransaction->Database();
   if (database->IsInvalidated()) {
     mAborted = true;
   }
 
-  IDBFactory::SetCurrentDatabase(database);
+  if (mConnection) {
+    IDBFactory::SetCurrentDatabase(database);
 
-  if (!mAborted) {
-    NS_NAMED_LITERAL_CSTRING(release, "END TRANSACTION");
-    if (NS_FAILED(mConnection->ExecuteSimpleSQL(release))) {
-      mAborted = PR_TRUE;
-    }
-  }
-
-  if (mAborted) {
-    NS_ASSERTION(mConnection, "This had better not be null!");
-
-    NS_NAMED_LITERAL_CSTRING(rollback, "ROLLBACK TRANSACTION");
-    if (NS_FAILED(mConnection->ExecuteSimpleSQL(rollback))) {
-      NS_WARNING("Failed to rollback transaction!");
+    if (!mAborted) {
+      NS_NAMED_LITERAL_CSTRING(release, "END TRANSACTION");
+      if (NS_FAILED(mConnection->ExecuteSimpleSQL(release))) {
+        mAborted = PR_TRUE;
+      }
     }
 
-    if (mTransaction->Mode() == nsIIDBTransaction::VERSION_CHANGE) {
-      nsresult rv =
-        IDBFactory::LoadDatabaseInformation(mConnection,
-                                            mTransaction->Database()->Id(),
-                                            mOldVersion, mOldObjectStores);
-      if (NS_SUCCEEDED(rv)) {
-        mHaveMetadata = true;
+    if (mAborted) {
+      NS_NAMED_LITERAL_CSTRING(rollback, "ROLLBACK TRANSACTION");
+      if (NS_FAILED(mConnection->ExecuteSimpleSQL(rollback))) {
+        NS_WARNING("Failed to rollback transaction!");
       }
-      else {
-        NS_WARNING("Failed to get database information!");
+
+      if (mTransaction->Mode() == nsIIDBTransaction::VERSION_CHANGE) {
+        nsresult rv =
+          IDBFactory::LoadDatabaseInformation(mConnection,
+                                              mTransaction->Database()->Id(),
+                                              mOldVersion, mOldObjectStores);
+        if (NS_SUCCEEDED(rv)) {
+          mHaveMetadata = true;
+        }
+        else {
+          NS_WARNING("Failed to get database information!");
+        }
       }
     }
   }
 
   mDoomedObjects.Clear();
 
-  mConnection->Close();
-  mConnection = nsnull;
+  if (mConnection) {
+    mConnection->Close();
+    mConnection = nsnull;
 
-  IDBFactory::SetCurrentDatabase(nsnull);
+    IDBFactory::SetCurrentDatabase(nsnull);
+  }
 
   return NS_OK;
 }
--- a/dom/indexedDB/IDBTransaction.h
+++ b/dom/indexedDB/IDBTransaction.h
@@ -133,24 +133,29 @@ public:
   already_AddRefed<mozIStorageStatement>
   GetCachedStatement(const char (&aQuery)[N])
   {
     nsCString query;
     query.AssignLiteral(aQuery);
     return GetCachedStatement(query);
   }
 
-  bool TransactionIsOpen() const;
+  bool IsOpen() const;
 
   bool IsWriteAllowed() const
   {
     return mMode == nsIIDBTransaction::READ_WRITE ||
            mMode == nsIIDBTransaction::VERSION_CHANGE;
   }
 
+  bool IsAborted() const
+  {
+    return mAborted;
+  }
+
   PRUint16 Mode()
   {
     return mMode;
   }
 
   IDBDatabase* Database()
   {
     NS_ASSERTION(mDatabase, "This should never be null!");
@@ -189,16 +194,20 @@ private:
 
   // Only touched on the database thread.
   PRUint32 mSavepointCount;
 
   nsTArray<nsRefPtr<IDBObjectStore> > mCreatedObjectStores;
 
   bool mAborted;
   bool mCreating;
+
+#ifdef DEBUG
+  bool mFiredCompleteOrAbort;
+#endif
 };
 
 class CommitHelper : public nsIRunnable
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIRUNNABLE
 
--- a/dom/indexedDB/test/Makefile.in
+++ b/dom/indexedDB/test/Makefile.in
@@ -80,16 +80,17 @@ TEST_FILES = \
   test_open_objectStore.html \
   test_overlapping_transactions.html \
   test_put_get_values.html \
   test_put_get_values_autoIncrement.html \
   test_readonly_transactions.html \
   test_remove_index.html \
   test_remove_objectStore.html \
   test_request_readyState.html \
+  test_success_events_after_abort.html \
   test_transaction_abort.html \
   test_transaction_lifetimes.html \
   test_transaction_lifetimes_nested.html \
   test_setVersion.html \
   test_setVersion_abort.html \
   test_setVersion_events.html \
   test_writer_starvation.html \
   $(NULL)
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_success_events_after_abort.html
@@ -0,0 +1,76 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Indexed Database Property Test</title>
+
+  <script type="text/javascript" src="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript;version=1.7">
+    function testSteps()
+    {
+      let request = moz_indexedDB.open(window.location.pathname);
+      request.onerror = errorHandler;
+      request.onsuccess = grabEventAndContinueHandler;
+      let event = yield;
+
+      let db = event.result;
+
+      let request = db.setVersion("1");
+      request.onsuccess = grabEventAndContinueHandler;
+      request.onerror = errorHandler;
+      event = yield;
+
+      event.result.oncomplete = continueToNextStep;
+
+      let objectStore = db.createObjectStore("foo");
+      objectStore.add({}, 1).onerror = errorHandler;
+
+      yield;
+
+      objectStore = db.transaction("foo").objectStore("foo");
+
+      let transaction = objectStore.transaction;
+      transaction.oncomplete = unexpectedSuccessHandler;
+      transaction.onabort = grabEventAndContinueHandler;
+
+      let sawError = false;
+
+      request = objectStore.get(1);
+      request.onsuccess = unexpectedSuccessHandler;
+      request.onerror = function(event) {
+        is(event.code, IDBDatabaseException.ABORT_ERR, "Good code");
+        sawError = true;
+        event.preventDefault();
+      }
+      
+      transaction.abort();
+
+      event = yield;
+
+      is(event.type, "abort", "Got abort event");
+      is(sawError, true, "Saw get() error");
+
+      // Make sure the success event isn't queued somehow.
+      netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+      var thread = Components.classes["@mozilla.org/thread-manager;1"]
+                             .getService(Components.interfaces.nsIThreadManager)
+                             .currentThread;
+      while (thread.hasPendingEvents()) {
+        thread.processNextEvent(false);
+      }
+
+      finishTest();
+      yield;
+    }
+  </script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
--- a/dom/indexedDB/test/test_transaction_abort.html
+++ b/dom/indexedDB/test/test_transaction_abort.html
@@ -202,42 +202,47 @@
       event = yield;
 
       is(event.result, undefined, "Object was removed");
 
       SimpleTest.executeSoon(function() { testGenerator.next(); });
       yield;
 
       let keys = [];
+      let abortEventCount = 0;
       objectStore = db.transaction("foo", READ_WRITE).objectStore("foo");
 
       for (let i = 0; i < 10; i++) {
         request = objectStore.add({});
-        request.onerror = errorHandler;
+        request.onerror = function(event) {
+          is(event.code, IDBDatabaseException.ABORT_ERR, "Good code");
+          abortEventCount++;
+          event.preventDefault();
+        };
         request.onsuccess = function(event) {
           keys.push(event.result);
           if (keys.length == 5) {
+            event.transaction.onabort = grabEventAndContinueHandler;
             event.transaction.abort();
           }
-          if (keys.length == 10) {
-            event.transaction.onabort = grabEventAndContinueHandler;
-          }
         };
       }
       event = yield;
 
-      is(keys.length, 10, "Not enough keys!");
+      is(event.type, "abort", "Got abort event");
+      is(keys.length, 5, "Added 5 items in this transaction");
+      is(abortEventCount, 5, "Got 5 abort error events");
 
-      for (let i = 0; i < 10; i++) {
+      for (let i in keys) {
         request = db.transaction("foo").objectStore("foo").get(keys[i]);
         request.onerror = errorHandler;
         request.onsuccess = grabEventAndContinueHandler;
         event = yield;
 
-        is(event.result, undefined, "Object was removed");
+        is(event.result, undefined, "Object was removed by abort");
       }
 
       finishTest();
       yield;
     }
   </script>
   <script type="text/javascript;version=1.7" src="helpers.js"></script>