Bug 692911: Refactor some VERSION_CHANGE transaction locking stuff and disallow transaction creation before databases are completely open. r=bent
☠☠ backed out by 767693e248aa ☠ ☠
authorKyle Huey <khuey@kylehuey.com>
Mon, 24 Oct 2011 16:01:11 -0400
changeset 79779 26ac81280f33998ce7492691ecabc0e872d007a2
parent 79778 58eb2ebcc7d2dcea0a8b2c01908513e204cfdd4d
child 79780 767693e248aa388febeb987291aece0ab7269c6e
push id506
push userclegnitto@mozilla.com
push dateWed, 09 Nov 2011 02:03:18 +0000
treeherdermozilla-aurora@63587fc7bb93 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbent
bugs692911
milestone10.0a1
Bug 692911: Refactor some VERSION_CHANGE transaction locking stuff and disallow transaction creation before databases are completely open. r=bent
dom/indexedDB/AsyncConnectionHelper.cpp
dom/indexedDB/AsyncConnectionHelper.h
dom/indexedDB/DatabaseInfo.cpp
dom/indexedDB/DatabaseInfo.h
dom/indexedDB/IDBDatabase.cpp
dom/indexedDB/IDBDatabase.h
dom/indexedDB/IndexedDatabaseManager.cpp
dom/indexedDB/IndexedDatabaseManager.h
dom/indexedDB/OpenDatabaseHelper.cpp
dom/indexedDB/OpenDatabaseHelper.h
dom/indexedDB/TransactionThreadPool.cpp
dom/indexedDB/TransactionThreadPool.h
dom/indexedDB/test/Makefile.in
dom/indexedDB/test/event_propagation_iframe.html
dom/indexedDB/test/leaving_page_iframe.html
dom/indexedDB/test/test_autoIncrement_indexes.html
dom/indexedDB/test/test_clear.html
dom/indexedDB/test/test_create_index_with_integer_keys.html
dom/indexedDB/test/test_cursor_mutation.html
dom/indexedDB/test/test_getAll.html
dom/indexedDB/test/test_index_getAll.html
dom/indexedDB/test/test_index_getAllObjects.html
dom/indexedDB/test/test_index_object_cursors.html
dom/indexedDB/test/test_indexes.html
dom/indexedDB/test/test_indexes_bad_values.html
dom/indexedDB/test/test_object_identity.html
dom/indexedDB/test/test_odd_result_order.html
dom/indexedDB/test/test_open_objectStore.html
dom/indexedDB/test/test_overlapping_transactions.html
dom/indexedDB/test/test_readonly_transactions.html
dom/indexedDB/test/test_setVersion_exclusion.html
dom/indexedDB/test/test_success_events_after_abort.html
dom/indexedDB/test/test_transaction_abort.html
dom/indexedDB/test/test_transaction_lifetimes.html
dom/indexedDB/test/test_transaction_lifetimes_nested.html
dom/indexedDB/test/test_writer_starvation.html
--- a/dom/indexedDB/AsyncConnectionHelper.cpp
+++ b/dom/indexedDB/AsyncConnectionHelper.cpp
@@ -555,18 +555,18 @@ NS_IMPL_QUERY_INTERFACE1(TransactionPool
 
 NS_IMETHODIMP
 TransactionPoolEventTarget::Dispatch(nsIRunnable* aRunnable,
                                      PRUint32 aFlags)
 {
   NS_ASSERTION(aRunnable, "Null pointer!");
   NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL, "Unsupported!");
 
-  TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate();
-  NS_ENSURE_TRUE(pool, NS_ERROR_FAILURE);
+  TransactionThreadPool* pool = TransactionThreadPool::Get();
+  NS_ASSERTION(pool, "This should never be null!");
 
   return pool->Dispatch(mTransaction, aRunnable, false, nsnull);
 }
 
 NS_IMETHODIMP
 TransactionPoolEventTarget::IsOnCurrentThread(bool* aResult)
 {
   *aResult = false;
--- a/dom/indexedDB/AsyncConnectionHelper.h
+++ b/dom/indexedDB/AsyncConnectionHelper.h
@@ -120,16 +120,21 @@ public:
   void SetError(nsresult aErrorCode)
   {
     NS_ASSERTION(NS_FAILED(aErrorCode), "Not a failure code!");
     mResultCode = aErrorCode;
   }
 
   static IDBTransaction* GetCurrentTransaction();
 
+  bool HasTransaction()
+  {
+    return mTransaction;
+  }
+
   nsISupports* GetSource()
   {
     return mRequest ? mRequest->Source() : nsnull;
   }
 
   nsresult GetResultCode()
   {
     return mResultCode;
--- a/dom/indexedDB/DatabaseInfo.cpp
+++ b/dom/indexedDB/DatabaseInfo.cpp
@@ -80,17 +80,18 @@ EnumerateObjectStoreNames(const nsAStrin
 }
 
 }
 
 #ifdef NS_BUILD_REFCNT_LOGGING
 DatabaseInfo::DatabaseInfo()
 : id(0),
   nextObjectStoreId(1),
-  nextIndexId(1)
+  nextIndexId(1),
+  runningVersionChange(false)
 {
   MOZ_COUNT_CTOR(DatabaseInfo);
 }
 
 DatabaseInfo::~DatabaseInfo()
 {
   MOZ_COUNT_DTOR(DatabaseInfo);
 }
--- a/dom/indexedDB/DatabaseInfo.h
+++ b/dom/indexedDB/DatabaseInfo.h
@@ -49,17 +49,18 @@ BEGIN_INDEXEDDB_NAMESPACE
 
 struct DatabaseInfo
 {
 #ifdef NS_BUILD_REFCNT_LOGGING
   DatabaseInfo();
   ~DatabaseInfo();
 #else
   DatabaseInfo()
-  : id(0), nextObjectStoreId(1), nextIndexId(1) { }
+  : id(0), nextObjectStoreId(1), nextIndexId(1), runningVersionChange(false)
+  { }
 #endif
 
   static bool Get(PRUint32 aId,
                   DatabaseInfo** aInfo);
 
   static bool Put(DatabaseInfo* aInfo);
 
   static void Remove(PRUint32 aId);
@@ -68,16 +69,17 @@ struct DatabaseInfo
   bool ContainsStoreName(const nsAString& aName);
 
   nsString name;
   PRUint64 version;
   PRUint32 id;
   nsString filePath;
   PRInt64 nextObjectStoreId;
   PRInt64 nextIndexId;
+  bool runningVersionChange;
 
   nsAutoRefCnt referenceCount;
 };
 
 struct IndexInfo
 {
 #ifdef NS_BUILD_REFCNT_LOGGING
   IndexInfo();
--- a/dom/indexedDB/IDBDatabase.cpp
+++ b/dom/indexedDB/IDBDatabase.cpp
@@ -424,16 +424,44 @@ IDBDatabase::CloseInternal()
 bool
 IDBDatabase::IsClosed()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   return mClosed;
 }
 
 void
+IDBDatabase::EnterSetVersionTransaction()
+{
+  DatabaseInfo* dbInfo;
+  if (!DatabaseInfo::Get(mDatabaseId, &dbInfo)) {
+    NS_ERROR("This should never fail!");
+  }
+
+  NS_ASSERTION(!dbInfo->runningVersionChange, "How did that happen?");
+  dbInfo->runningVersionChange = true;
+}
+
+void
+IDBDatabase::ExitSetVersionTransaction()
+{
+  DatabaseInfo* dbInfo;
+  if (!DatabaseInfo::Get(mDatabaseId, &dbInfo)) {
+    NS_ERROR("This should never fail!");
+  }
+
+  NS_ASSERTION(dbInfo->runningVersionChange, "How did that happen?");
+  dbInfo->runningVersionChange = false;
+
+  IndexedDatabaseManager* manager = IndexedDatabaseManager::Get();
+  NS_ASSERTION(manager, "We should always have a manager here");
+  manager->UnblockSetVersionRunnable(this);
+}
+
+void
 IDBDatabase::OnUnlink()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(!mOwner, "Should have been cleared already!");
 
   // We've been unlinked, at the very least we should be able to prevent further
   // transactions from starting and unblock any other SetVersion callers.
   Close();
@@ -705,16 +733,20 @@ IDBDatabase::Transaction(nsIVariant* aSt
   nsresult rv = aStoreNames->GetDataType(&type);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   DatabaseInfo* info;
   if (!DatabaseInfo::Get(mDatabaseId, &info)) {
     NS_ERROR("This should never fail!");
   }
 
+  if (info->runningVersionChange) {
+    return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
+  }
+
   nsTArray<nsString> storesToOpen;
 
   switch (type) {
     case nsIDataType::VTYPE_VOID:
     case nsIDataType::VTYPE_EMPTY:
     case nsIDataType::VTYPE_EMPTY_ARRAY: {
       // Empty, request all object stores
       if (!info->GetObjectStoreNames(storesToOpen)) {
--- a/dom/indexedDB/IDBDatabase.h
+++ b/dom/indexedDB/IDBDatabase.h
@@ -130,16 +130,19 @@ public:
   // transactions for this database will be allowed to run.
   bool IsInvalidated();
 
   void CloseInternal();
 
   // Whether or not the database has had Close called on it.
   bool IsClosed();
 
+  void EnterSetVersionTransaction();
+  void ExitSetVersionTransaction();
+
 private:
   IDBDatabase();
   ~IDBDatabase();
 
   void OnUnlink();
 
   PRUint32 mDatabaseId;
   nsString mName;
--- a/dom/indexedDB/IndexedDatabaseManager.cpp
+++ b/dom/indexedDB/IndexedDatabaseManager.cpp
@@ -688,47 +688,64 @@ IndexedDatabaseManager::OnDatabaseClosed
       // for other databases the mDatabases array may be empty.
       if (!runnable->mDatabases.IsEmpty() &&
           !runnable->mDatabases.RemoveElement(aDatabase)) {
         NS_ERROR("Didn't have this database in our list!");
       }
 
       // Now run the helper if there are no more live databases.
       if (runnable->mHelper && runnable->mDatabases.IsEmpty()) {
-        // Don't hold the callback alive longer than necessary.
-        nsRefPtr<AsyncConnectionHelper> helper;
-        helper.swap(runnable->mHelper);
+        // At this point, all databases are closed, so no new transactions can
+        // be started.  There may, however, still be outstanding transactions
+        // that have not completed.  We need to wait for those before we
+        // dispatch the helper.
 
-        if (NS_FAILED(helper->DispatchToTransactionPool())) {
-          NS_WARNING("Failed to dispatch to thread pool!");
-        }
+        TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate();
 
-        // Now wait for the transaction to complete. Completing the transaction
-        // will be our cue to remove the SetVersionRunnable from our list and
-        // therefore allow other SetVersion requests to begin.
-        TransactionThreadPool* pool = TransactionThreadPool::Get();
-        NS_ASSERTION(pool, "This should never be null!");
+        nsRefPtr<WaitForTransactionsToFinishRunnable> waitRunnable =
+          new WaitForTransactionsToFinishRunnable(runnable);
 
         // All other databases should be closed, so we only need to wait on this
         // one.
         nsAutoTArray<nsRefPtr<IDBDatabase>, 1> array;
         if (!array.AppendElement(aDatabase)) {
           NS_ERROR("This should never fail!");
         }
 
-        // Use the SetVersionRunnable as the callback.
-        if (!pool->WaitForAllDatabasesToComplete(array, runnable)) {
+        // Use the WaitForTransactionsToFinishRunnable as the callback.
+        if (!pool->WaitForAllDatabasesToComplete(array, waitRunnable)) {
           NS_WARNING("Failed to wait for transaction to complete!");
         }
       }
       break;
     }
   }
 }
 
+void
+IndexedDatabaseManager::UnblockSetVersionRunnable(IDBDatabase* aDatabase)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(aDatabase, "Null pointer!");
+
+  // Check through the list of SetVersionRunnables to find the one we're seeking.
+  for (PRUint32 index = 0; index < mSetVersionRunnables.Length(); index++) {
+    nsRefPtr<SetVersionRunnable>& runnable = mSetVersionRunnables[index];
+
+    if (runnable->mRequestingDatabase->Id() == aDatabase->Id()) {
+      NS_ASSERTION(!runnable->mHelper,
+                 "Why are we unblocking a runnable if the helper didn't run?");
+      NS_DispatchToCurrentThread(runnable);
+      return;
+    }
+  }
+
+  NS_NOTREACHED("How did we get here!");
+}
+
 // static
 bool
 IndexedDatabaseManager::SetCurrentDatabase(IDBDatabase* aDatabase)
 {
   NS_ASSERTION(gCurrentDatabaseIndex != BAD_TLS_INDEX,
                "This should have been set already!");
 
 #ifdef DEBUG
@@ -1278,8 +1295,44 @@ IndexedDatabaseManager::SetVersionRunnab
   NS_ASSERTION(mgr, "This should never be null!");
 
   // Let the IndexedDatabaseManager know that the SetVersion transaction has
   // completed.
   mgr->OnSetVersionRunnableComplete(this);
 
   return NS_OK;
 }
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(IndexedDatabaseManager::WaitForTransactionsToFinishRunnable,
+                              nsIRunnable)
+
+NS_IMETHODIMP
+IndexedDatabaseManager::WaitForTransactionsToFinishRunnable::Run()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  // Don't hold the callback alive longer than necessary.
+  nsRefPtr<AsyncConnectionHelper> helper;
+  helper.swap(mRunnable->mHelper);
+
+  nsRefPtr<SetVersionRunnable> runnable;
+  runnable.swap(mRunnable);
+
+  // If the helper has a transaction, dispatch it to the transaction
+  // threadpool.
+  if (helper->HasTransaction()) {
+    if (NS_FAILED(helper->DispatchToTransactionPool())) {
+      NS_WARNING("Failed to dispatch to thread pool!");
+    }
+  }
+  // Otherwise, dispatch it to the IO thread.
+  else {
+    IndexedDatabaseManager* manager = IndexedDatabaseManager::Get();
+    NS_ASSERTION(manager, "We should definitely have a manager here");
+
+    helper->Dispatch(manager->IOThread());
+  }
+
+  // The helper is responsible for calling
+  // IndexedDatabaseManager::UnblockSetVersionRunnable.
+
+  return NS_OK;
+}
--- a/dom/indexedDB/IndexedDatabaseManager.h
+++ b/dom/indexedDB/IndexedDatabaseManager.h
@@ -199,16 +199,18 @@ private:
     nsCOMPtr<nsIIndexedDatabaseUsageCallback> mCallback;
     PRUint64 mUsage;
     PRInt32 mCanceled;
   };
 
   // Called when AsyncUsageRunnable has finished its Run() method.
   inline void OnUsageCheckComplete(AsyncUsageRunnable* aRunnable);
 
+  void UnblockSetVersionRunnable(IDBDatabase* aDatabase);
+
   // Responsible for waiting until all databases have been closed before running
   // the version change transaction. Created when
   // IndexedDatabaseManager::SetDatabaseVersion is called. Runs only once on the
   // main thread when the version change transaction has completed.
   class SetVersionRunnable : public nsIRunnable
   {
   public:
     NS_DECL_ISUPPORTS
@@ -222,16 +224,36 @@ private:
     nsTArray<nsRefPtr<IDBDatabase> > mDatabases;
     nsRefPtr<AsyncConnectionHelper> mHelper;
     nsTArray<nsCOMPtr<nsIRunnable> > mDelayedRunnables;
   };
 
   // Called when SetVersionRunnable has finished its Run() method.
   inline void OnSetVersionRunnableComplete(SetVersionRunnable* aRunnable);
 
+
+  // A callback runnable used by the TransactionPool when it's safe to proceed
+  // with a SetVersion/DeleteDatabase/etc.
+  class WaitForTransactionsToFinishRunnable : public nsIRunnable
+  {
+  public:
+    WaitForTransactionsToFinishRunnable(SetVersionRunnable* aRunnable)
+    : mRunnable(aRunnable)
+    {
+      NS_ASSERTION(mRunnable, "Why don't we have a runnable?");
+      NS_ASSERTION(mRunnable->mDatabases.IsEmpty(), "We're here too early!");
+    }
+
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIRUNNABLE
+
+  private:
+    nsRefPtr<SetVersionRunnable> mRunnable;
+  };
+
   // Maintains a list of live databases per origin.
   nsClassHashtable<nsCStringHashKey, nsTArray<IDBDatabase*> > mLiveDatabases;
 
   // Maintains a list of origins that are currently being cleared.
   nsAutoTArray<nsRefPtr<OriginClearRunnable>, 1> mOriginClearRunnables;
 
   // Maintains a list of origins that we're currently enumerating to gather
   // usage statistics.
--- a/dom/indexedDB/OpenDatabaseHelper.cpp
+++ b/dom/indexedDB/OpenDatabaseHelper.cpp
@@ -490,20 +490,23 @@ public:
     mRequestedVersion(aRequestedVersion),
     mCurrentVersion(aCurrentVersion)
   {
     mTransaction->SetTransactionListener(this);
   }
 
   NS_DECL_ISUPPORTS_INHERITED
 
-  nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
   nsresult GetSuccessResult(JSContext* aCx,
                             jsval* aVal);
 
+protected:
+  nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
+  nsresult Init();
+
   // SetVersionHelper never fires an error event at the request.  It hands that
   // responsibility back to the OpenDatabaseHelper
   void OnError() { }
 
   // Need an upgradeneeded event here.
   already_AddRefed<nsDOMEvent> CreateSuccessEvent();
 
   nsresult NotifyTransactionComplete(IDBTransaction* aTransaction);
@@ -679,16 +682,17 @@ OpenDatabaseHelper::StartSetVersion()
 
   rv = mgr->SetDatabaseVersion(mDatabase, mOpenDBRequest, mCurrentVersion,
                                mRequestedVersion, helper);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   // The SetVersionHelper is responsible for dispatching us back to the
   // main thread again and changing the state to eSetVersionCompleted.
   mState = eSetVersionPending;
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 OpenDatabaseHelper::Run()
 {
   NS_ASSERTION(mState != eCreated, "Dispatch was not called?!?");
 
@@ -706,16 +710,22 @@ OpenDatabaseHelper::Run()
     }
 
     // We've done whatever work we need to do on the DB thread, and any
     // SetVersion stuff is done by now.
     NS_ASSERTION(mState == eFiringEvents ||
                  mState == eSetVersionCompleted, "Why are we here?");
 
     if (mState == eSetVersionCompleted) {
+      // Allow transaction creation/other version change transactions to proceed
+      // before we fire events.  Other version changes will be postd to the end
+      // of the event loop, and will be behind whatever the page does in
+      // its error/success event handlers.
+      mDatabase->ExitSetVersionTransaction();
+
       mState = eFiringEvents;
     } else {
       // Notify the request that we're done, but only if we didn't just finish
       // a SetVersionHelper.  In the SetVersionHelper case, that helper tells
       // the request that it is done, and we avoid calling NotifyHandlerCompleted
       // twice.
 
       nsresult rv = mOpenDBRequest->NotifyHelperCompleted(this);
@@ -861,16 +871,25 @@ OpenDatabaseHelper::NotifySetVersionFini
 
   mState = eSetVersionCompleted;
   
   // Dispatch ourself back to the main thread
   return NS_DispatchToCurrentThread(this);
 }
 
 void
+OpenDatabaseHelper::BlockDatabase()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(mDatabase, "This is going bad fast.");
+
+  mDatabase->EnterSetVersionTransaction();
+}
+
+void
 OpenDatabaseHelper::DispatchSuccessEvent()
 {
   NS_ASSERTION(mDatabase, "Doesn't seem very successful to me.");
 
   nsRefPtr<nsDOMEvent> event =
     CreateGenericEvent(NS_LITERAL_STRING(SUCCESS_EVT_STR));
   if (!event) {
     NS_ERROR("Failed to create event!");
@@ -912,16 +931,25 @@ OpenDatabaseHelper::ReleaseMainThreadObj
   mDatabase = nsnull;
 
   HelperBase::ReleaseMainThreadObjects();
 }
 
 NS_IMPL_ISUPPORTS_INHERITED0(SetVersionHelper, AsyncConnectionHelper);
 
 nsresult
+SetVersionHelper::Init()
+{
+  // Block transaction creation until we are done.
+  mOpenHelper->BlockDatabase();
+
+  return NS_OK;
+}
+
+nsresult
 SetVersionHelper::DoDatabaseWork(mozIStorageConnection* aConnection)
 {
   NS_ASSERTION(aConnection, "Passing a null connection!");
 
   nsCOMPtr<mozIStorageStatement> stmt;
   nsresult rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
     "UPDATE database "
     "SET version = :version"
--- a/dom/indexedDB/OpenDatabaseHelper.h
+++ b/dom/indexedDB/OpenDatabaseHelper.h
@@ -77,16 +77,17 @@ public:
   }
 
   nsresult GetResultCode()
   {
     return mResultCode;
   }
 
   nsresult NotifySetVersionFinished();
+  void BlockDatabase();
 
 protected:
   // Methods only called on the main thread
   nsresult EnsureSuccessResult();
   nsresult StartSetVersion();
   nsresult GetSuccessResult(JSContext* aCx,
                           jsval* aVal);
   void DispatchSuccessEvent();
--- a/dom/indexedDB/TransactionThreadPool.cpp
+++ b/dom/indexedDB/TransactionThreadPool.cpp
@@ -233,17 +233,16 @@ TransactionThreadPool::FinishTransaction
 
   nsTArray<TransactionInfo>& transactionsInProgress =
     dbTransactionInfo->transactions;
 
   PRUint32 transactionCount = transactionsInProgress.Length();
 
 #ifdef DEBUG
   if (aTransaction->mMode == IDBTransaction::VERSION_CHANGE) {
-    NS_ASSERTION(dbTransactionInfo->locked, "Should be locked!");
     NS_ASSERTION(transactionCount == 1,
                  "More transactions running than should be!");
   }
 #endif
 
   if (transactionCount == 1) {
 #ifdef DEBUG
     {
@@ -339,35 +338,27 @@ TransactionThreadPool::TransactionCanRun
   }
 
   nsTArray<TransactionInfo>& transactionsInProgress =
     dbTransactionInfo->transactions;
 
   PRUint32 transactionCount = transactionsInProgress.Length();
   NS_ASSERTION(transactionCount, "Should never be 0!");
 
-  if (mode == IDBTransaction::VERSION_CHANGE) {
-    dbTransactionInfo->lockPending = true;
-  }
-
   for (PRUint32 index = 0; index < transactionCount; index++) {
     // See if this transaction is in out list of current transactions.
     const TransactionInfo& info = transactionsInProgress[index];
     if (info.transaction == aTransaction) {
       *aCanRun = true;
       *aExistingQueue = info.queue;
       return NS_OK;
     }
   }
 
-  if (dbTransactionInfo->locked || dbTransactionInfo->lockPending) {
-    *aCanRun = false;
-    *aExistingQueue = nsnull;
-    return NS_OK;
-  }
+  NS_ASSERTION(mode != IDBTransaction::VERSION_CHANGE, "How did we get here?");
 
   bool writeOverlap;
   nsresult rv =
     CheckOverlapAndMergeObjectStores(dbTransactionInfo->storesWriting,
                                      objectStoreNames,
                                      mode == nsIIDBTransaction::READ_WRITE,
                                      &writeOverlap);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -443,21 +434,16 @@ TransactionThreadPool::Dispatch(IDBTrans
   nsAutoPtr<DatabaseTransactionInfo> autoDBTransactionInfo;
 
   if (!mTransactionsInProgress.Get(databaseId, &dbTransactionInfo)) {
     // Make a new struct for this transaction.
     autoDBTransactionInfo = new DatabaseTransactionInfo();
     dbTransactionInfo = autoDBTransactionInfo;
   }
 
-  if (aTransaction->mMode == IDBTransaction::VERSION_CHANGE) {
-    NS_ASSERTION(!dbTransactionInfo->locked, "Already locked?!");
-    dbTransactionInfo->locked = true;
-  }
-
   const nsTArray<nsString>& objectStoreNames = aTransaction->mObjectStoreNames;
 
   nsTArray<nsString>& storesInUse =
     aTransaction->mMode == nsIIDBTransaction::READ_WRITE ?
     dbTransactionInfo->storesWriting :
     dbTransactionInfo->storesReading;
 
   if (!storesInUse.AppendElements(objectStoreNames)) {
--- a/dom/indexedDB/TransactionThreadPool.h
+++ b/dom/indexedDB/TransactionThreadPool.h
@@ -118,22 +118,16 @@ protected:
   {
     nsRefPtr<IDBTransaction> transaction;
     nsRefPtr<TransactionQueue> queue;
     nsTArray<nsString> objectStoreNames;
   };
 
   struct DatabaseTransactionInfo
   {
-    DatabaseTransactionInfo()
-    : locked(false), lockPending(false)
-    { }
-
-    bool locked;
-    bool lockPending;
     nsTArray<TransactionInfo> transactions;
     nsTArray<nsString> storesReading;
     nsTArray<nsString> storesWriting;
   };
 
   struct QueuedDispatchInfo
   {
     QueuedDispatchInfo()
--- a/dom/indexedDB/test/Makefile.in
+++ b/dom/indexedDB/test/Makefile.in
@@ -93,16 +93,17 @@ TEST_FILES = \
   test_success_events_after_abort.html \
   test_third_party.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_setVersion_exclusion.html \
   test_writer_starvation.html \
   third_party_iframe1.html \
   third_party_iframe2.html \
   $(NULL)
 
 ifeq (browser,$(MOZ_BUILD_APP))
 BROWSER_TEST_FILES = \
   browser_forgetThisSite.js \
--- a/dom/indexedDB/test/event_propagation_iframe.html
+++ b/dom/indexedDB/test/event_propagation_iframe.html
@@ -99,17 +99,17 @@
       request.onerror = errorHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
       let event = yield;
 
       let db = event.target.result;
       db.onerror = errorEventCounter;
       db.addEventListener("error", errorEventCounter, true);
 
-      event.target.transaction.oncomplete = grabEventAndContinueHandler;
+      event.target.onsuccess = grabEventAndContinueHandler;
 
       db.createObjectStore("foo", { autoIncrement: true });
       yield;
 
       let transaction = db.transaction("foo", IDBTransaction.READ_WRITE);
       transaction.addEventListener("error", errorEventCounter, false);
       transaction.addEventListener("error", errorEventCounter, true);
 
--- a/dom/indexedDB/test/leaving_page_iframe.html
+++ b/dom/indexedDB/test/leaving_page_iframe.html
@@ -7,17 +7,17 @@ function startDBWork() {
   mozIndexedDB.open(parent.location, 1).onupgradeneeded = function(e) {
     db = e.target.result;
     var trans = e.target.transaction;
     if (db.objectStoreNames.contains("mystore")) {
       db.deleteObjectStore("mystore");
     }
     var store = db.createObjectStore("mystore");
     store.add({ hello: "world" }, 42);
-    trans.oncomplete = madeMod;
+    e.target.onsuccess = madeMod;
   };
 }
 
 function madeMod() {
   var trans = db.transaction(["mystore"], IDBTransaction.READ_WRITE);
   var store = trans.
               objectStore("mystore");
   trans.oncomplete = function() {
--- a/dom/indexedDB/test/test_autoIncrement_indexes.html
+++ b/dom/indexedDB/test/test_autoIncrement_indexes.html
@@ -28,16 +28,19 @@
 
       let data = { first: "foo", second: "foo", third: "foo" };
 
       objectStore.add(data).onsuccess = grabEventAndContinueHandler;
       event = yield;
 
       let key = event.target.result;
       ok(key, "Added entry");
+      request.onsuccess = grabEventAndContinueHandler;
+
+      event = yield;
 
       let objectStore = db.transaction("foo").objectStore("foo");
       let first = objectStore.index("first");
       let second = objectStore.index("second");
       let third = objectStore.index("third");
 
       first.get("foo").onsuccess = grabEventAndContinueHandler;
       event = yield;
--- a/dom/indexedDB/test/test_clear.html
+++ b/dom/indexedDB/test/test_clear.html
@@ -20,17 +20,17 @@
 
       let request = mozIndexedDB.open(name, 1, description);
       request.onerror = errorHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
       let event = yield;
 
       let db = request.result;
 
-      event.target.transaction.oncomplete = continueToNextStep;
+      event.target.onsuccess = continueToNextStep;
 
       let objectStore = db.createObjectStore("foo", { autoIncrement: true });
 
       let firstKey;
       for (let i = 0; i < entryCount; i++) {
         request = objectStore.add({});
         request.onerror = errorHandler;
         if (!i) {
--- a/dom/indexedDB/test/test_create_index_with_integer_keys.html
+++ b/dom/indexedDB/test/test_create_index_with_integer_keys.html
@@ -18,33 +18,33 @@
       let request = mozIndexedDB.open(window.location.pathname, 1);
       request.onerror = errorHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
       let event = yield;
 
       let db = event.target.result;
       db.onerror = errorHandler;
 
-      event.target.transaction.oncomplete = continueToNextStep;
+      event.target.onsuccess = continueToNextStep;
 
       // Make object store, add data.
       let objectStore = db.createObjectStore("foo", { keyPath: "id" });
       objectStore.add(data);
       yield;
       db.close();
 
       let request = mozIndexedDB.open(window.location.pathname, 2);
       request.onerror = errorHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
       let event = yield;
 
       let db2 = event.target.result;
       db2.onerror = errorHandler;
 
-      event.target.transaction.oncomplete = continueToNextStep;
+      event.target.onsuccess = continueToNextStep;
 
       // Create index.
       event.target.transaction.objectStore("foo").createIndex("foo", "num");
       yield;
 
       // Make sure our object made it into the index.
       let seenCount = 0;
 
--- a/dom/indexedDB/test/test_cursor_mutation.html
+++ b/dom/indexedDB/test/test_cursor_mutation.html
@@ -30,17 +30,17 @@
       const objectStoreDataNameSort = [ 1, 4, 5, 2, 3 ];
 
       let request = mozIndexedDB.open(window.location.pathname, 1);
       request.onerror = errorHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
       let event = yield;
 
       let db = event.target.result;
-      event.target.transaction.oncomplete = continueToNextStep;
+      event.target.onsuccess = continueToNextStep;
 
       let objectStore = db.createObjectStore("foo", { keyPath: "ss" });
       objectStore.createIndex("name", "name", { unique: true });
 
       for (let i = 0; i < objectStoreData.length - 1; i++) {
         objectStore.add(objectStoreData[i]);
       }
       yield;
--- a/dom/indexedDB/test/test_getAll.html
+++ b/dom/indexedDB/test/test_getAll.html
@@ -21,16 +21,17 @@
       request.onerror = errorHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
       let event = yield;
 
       let db = event.target.result;
 
       let objectStore = db.createObjectStore("foo", { autoIncrement: true });
 
+      request.onsuccess = grabEventAndContinueHandler;
       request = objectStore.getAll();
       request.onerror = errorHandler;
       request.onsuccess = grabEventAndContinueHandler;
       event = yield;
 
       is(event.target.result instanceof Array, true, "Got an array object");
       is(event.target.result.length, 0, "No elements");
 
@@ -41,16 +42,17 @@
         request.onerror = errorHandler;
         request.onsuccess = function(event) {
           if (++addedCount == values.length) {
             SimpleTest.executeSoon(function() { testGenerator.next(); });
           }
         }
       }
       yield;
+      yield;
 
       request = db.transaction("foo").objectStore("foo").getAll();
       request.onerror = errorHandler;
       request.onsuccess = grabEventAndContinueHandler;
       event = yield;
 
       is(event.target.result instanceof Array, true, "Got an array object");
       is(event.target.result.length, values.length, "Same length");
--- a/dom/indexedDB/test/test_index_getAll.html
+++ b/dom/indexedDB/test/test_index_getAll.html
@@ -55,16 +55,17 @@
         { key: "237-23-7736", value: { name: "Joe", height: 65, weight: 150 } },
         { key: "237-23-7737", value: { name: "Pat", height: 65 } },
         { key: "237-23-7734", value: { name: "Ron", height: 73, weight: 180 } }
       ];
 
       let request = mozIndexedDB.open(name, 1, description);
       request.onerror = errorHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
+      request.onsuccess = grabEventAndContinueHandler;
       let event = yield;
 
       let db = event.target.result;
 
       let objectStore = db.createObjectStore(objectStoreName);
 
       // First, add all our data to the object store.
       let addedData = 0;
@@ -73,27 +74,26 @@
                                   objectStoreData[i].key);
         request.onerror = errorHandler;
         request.onsuccess = function(event) {
           if (++addedData == objectStoreData.length) {
             testGenerator.send(event);
           }
         }
       }
-      event = yield;
+      yield;
       ok(true, "1");
 
       // Now create the indexes.
       for (let i in indexData) {
         objectStore.createIndex(indexData[i].name, indexData[i].keyPath,
                                 indexData[i].options);
       }
 
       is(objectStore.indexNames.length, indexData.length, "Good index count");
-      continueToNextStep();
       yield;
 
       ok(true, "2");
       objectStore = db.transaction(objectStoreName)
                       .objectStore(objectStoreName);
 
       request = objectStore.index("height").getAllKeys(65);
       request.onerror = errorHandler;
--- a/dom/indexedDB/test/test_index_getAllObjects.html
+++ b/dom/indexedDB/test/test_index_getAllObjects.html
@@ -55,16 +55,17 @@
         { key: "237-23-7736", value: { name: "Joe", height: 65, weight: 150 } },
         { key: "237-23-7737", value: { name: "Pat", height: 65 } },
         { key: "237-23-7734", value: { name: "Ron", height: 73, weight: 180 } }
       ];
 
       let request = mozIndexedDB.open(name, 1, description);
       request.onerror = errorHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
+      request.onsuccess = grabEventAndContinueHandler;
       let event = yield;
 
       let db = event.target.result;
 
       let objectStore = db.createObjectStore(objectStoreName, {});
 
       // First, add all our data to the object store.
       let addedData = 0;
@@ -82,17 +83,16 @@
 
       // Now create the indexes.
       for (let i in indexData) {
         objectStore.createIndex(indexData[i].name, indexData[i].keyPath,
                                 indexData[i].options);
       }
 
       is(objectStore.indexNames.length, indexData.length, "Good index count");
-      continueToNextStep();
       yield;
 
       objectStore = db.transaction(objectStoreName)
                       .objectStore(objectStoreName);
 
       request = objectStore.index("height").getAll(65);
       request.onerror = errorHandler;
       request.onsuccess = grabEventAndContinueHandler;
--- a/dom/indexedDB/test/test_index_object_cursors.html
+++ b/dom/indexedDB/test/test_index_object_cursors.html
@@ -32,17 +32,17 @@
       let request = mozIndexedDB.open(window.location.pathname, 1);
       request.onerror = errorHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
       let event = yield;
 
       let db = event.target.result;
       db.onerror = errorHandler;
 
-      event.target.transaction.oncomplete = continueToNextStep;
+      event.target.onsuccess = continueToNextStep;
 
       for (let objectStoreIndex in objectStoreData) {
         const objectStoreInfo = objectStoreData[objectStoreIndex];
         let objectStore = db.createObjectStore(objectStoreInfo.name,
                                                objectStoreInfo.options);
         for (let indexIndex in indexData) {
           const indexInfo = indexData[indexIndex];
           let index = objectStore.createIndex(indexInfo.name,
--- a/dom/indexedDB/test/test_indexes.html
+++ b/dom/indexedDB/test/test_indexes.html
@@ -66,16 +66,17 @@
         { key: "237-23-7736", value: { name: "Joe", height: 65, weight: 150 } },
         { key: "237-23-7737", value: { name: "Pat", height: 65 } },
         { key: "237-23-7734", value: { name: "Ron", height: 73, weight: 180 } }
       ];
 
       let request = mozIndexedDB.open(name, 1, description);
       request.onerror = errorHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
+      request.onsuccess = grabEventAndContinueHandler;
       let event = yield;
 
       let db = event.target.result;
 
       let objectStore = db.createObjectStore(objectStoreName, { keyPath: "" });
 
       // First, add all our data to the object store.
       let addedData = 0;
@@ -92,17 +93,16 @@
       event = yield;
 
       // Now create the indexes.
       for (let i in indexData) {
         objectStore.createIndex(indexData[i].name, indexData[i].keyPath,
                                 indexData[i].options);
       }
       is(objectStore.indexNames.length, indexData.length, "Good index count");
-      continueToNextStep();
       yield;
 
       objectStore = db.transaction(objectStoreName)
                       .objectStore(objectStoreName);
 
       // Check global properties to make sure they are correct.
       is(objectStore.indexNames.length, indexData.length, "Good index count");
       for (let i in indexData) {
--- a/dom/indexedDB/test/test_indexes_bad_values.html
+++ b/dom/indexedDB/test/test_indexes_bad_values.html
@@ -46,16 +46,17 @@
         { key: "237-23-7735", value: { name: "Sue", height: 58, weight: 130 } },
         { key: "237-23-7736", value: { name: "Joe", height: 65, weight: 150 } },
         { key: "237-23-7734", value: { name: "Ron", height: 73, weight: 180 } }
       ];
 
       let request = mozIndexedDB.open(name, 1, description);
       request.onerror = errorHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
+      request.onsuccess = grabEventAndContinueHandler;
       let event = yield;
 
       let db = event.target.result;
 
       let objectStore = db.createObjectStore(objectStoreName, { } );
 
       let addedData = 0;
       for (let i in objectStoreData) {
@@ -82,16 +83,17 @@
         request.onerror = errorHandler;
         request.onsuccess = function(event) {
           if (++addedData == badObjectStoreData.length) {
             SimpleTest.executeSoon(function() { testGenerator.next() });
           }
         }
       }
       yield;
+      yield;
 
       objectStore = db.transaction(objectStoreName)
                       .objectStore(objectStoreName);
 
       let keyIndex = 0;
 
       request = objectStore.index("weight").openKeyCursor();
       request.onerror = errorHandler;
--- a/dom/indexedDB/test/test_object_identity.html
+++ b/dom/indexedDB/test/test_object_identity.html
@@ -23,17 +23,17 @@
       let objectStore1 = db.createObjectStore("foo");
       let objectStore2 = transaction.objectStore("foo");
       ok(objectStore1 === objectStore2, "Got same objectStores");
 
       let index1 = objectStore1.createIndex("bar", "key");
       let index2 = objectStore2.index("bar");
       ok(index1 === index2, "Got same indexes");
 
-      transaction.oncomplete = continueToNextStep;
+      request.onsuccess = continueToNextStep;
       yield;
 
       transaction = db.transaction("foo");
 
       let objectStore3 = transaction.objectStore("foo");
       let objectStore4 = transaction.objectStore("foo");
       ok(objectStore3 === objectStore4, "Got same objectStores");
 
--- a/dom/indexedDB/test/test_odd_result_order.html
+++ b/dom/indexedDB/test/test_odd_result_order.html
@@ -24,17 +24,17 @@
       ok(db instanceof IDBDatabase, "Got a real database");
 
       db.onerror = errorHandler;
 
       let objectStore = db.createObjectStore("foo", { keyPath: "key",
                                                       autoIncrement: true });
       let index = objectStore.createIndex("foo", "index");
 
-      event.target.transaction.oncomplete = continueToNextStep;
+      event.target.onsuccess = continueToNextStep;
       yield;
 
       objectStore = db.transaction("foo", IDBTransaction.READ_WRITE)
                       .objectStore("foo");
       request = objectStore.add(data);
       request.onsuccess = grabEventAndContinueHandler;
       event = yield;
 
--- a/dom/indexedDB/test/test_open_objectStore.html
+++ b/dom/indexedDB/test/test_open_objectStore.html
@@ -15,28 +15,28 @@
       const nsIIDBObjectStore = Components.interfaces.nsIIDBObjectStore;
       const name = window.location.pathname;
       const description = "My Test Database";
       const objectStoreName = "Objects";
 
       let request = mozIndexedDB.open(name, 1, description);
       request.onerror = errorHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
+      request.onsuccess = grabEventAndContinueHandler;
       let event = yield;
 
       let db = event.target.result;
       is(db.objectStoreNames.length, 0, "Bad objectStores list");
 
       let objectStore = db.createObjectStore(objectStoreName,
                                              { keyPath: "foo" });
 
       is(db.objectStoreNames.length, 1, "Bad objectStores list");
       is(db.objectStoreNames.item(0), objectStoreName, "Bad name");
 
-      continueToNextStep();
       yield;
 
       objectStore = db.transaction(objectStoreName).objectStore(objectStoreName);
 
       is(objectStore.name, objectStoreName, "Bad name");
       is(objectStore.keyPath, "foo", "Bad keyPath");
       if(objectStore.indexNames.length, 0, "Bad indexNames");
 
--- a/dom/indexedDB/test/test_overlapping_transactions.html
+++ b/dom/indexedDB/test/test_overlapping_transactions.html
@@ -21,17 +21,17 @@
       let request = mozIndexedDB.open(name, 1, description);
       request.onerror = errorHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
       let event = yield;
 
       let db = event.target.result;
       is(db.objectStoreNames.length, 0, "Correct objectStoreNames list");
 
-      event.target.transaction.oncomplete = grabEventAndContinueHandler;
+      event.target.onsuccess = grabEventAndContinueHandler;
       for (let i in objectStores) {
         db.createObjectStore(objectStores[i], { autoIncrement: true });
       }
       let event = yield;
 
       is(db.objectStoreNames.length, objectStores.length,
          "Correct objectStoreNames list");
 
--- a/dom/indexedDB/test/test_readonly_transactions.html
+++ b/dom/indexedDB/test/test_readonly_transactions.html
@@ -17,23 +17,26 @@
 
       const name = window.location.pathname;
       const description = "My Test Database";
       const osName = "foo";
 
       let request = mozIndexedDB.open(name, 1, description);
       request.onerror = errorHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
+      request.onsuccess = grabEventAndContinueHandler;
       let event = yield;
 
       let db = event.target.result;
       is(db.objectStoreNames.length, 0, "Correct objectStoreNames list");
 
       db.createObjectStore(osName, { autoIncrement: "true" });
 
+      yield;
+
       let key1, key2;
 
       request = db.transaction([osName], READ_WRITE)
                   .objectStore(osName)
                   .add({});
       request.onerror = errorHandler;
       request.onsuccess = function(event) {
         is(event.target.transaction.mode, READ_WRITE, "Correct mode");
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_setVersion_exclusion.html
@@ -0,0 +1,90 @@
+<!--
+  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="/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()
+    {
+      const name = window.location.pathname;
+
+      let request = mozIndexedDB.open(name, 1);
+      request.onerror = errorHandler;
+      request.onupgradeneeded = grabEventAndContinueHandler;
+      request.onsuccess = unexpectedSuccessHandler;
+
+      let request2 = mozIndexedDB.open(name, 2);
+      request2.onerror = errorHandler;
+      request2.onupgradeneeded = unexpectedSuccessHandler;
+
+      let event = yield;
+      is(event.type, "upgradeneeded", "Expect an upgradeneeded event");
+      is(event.target, request, "Event should be fired on the request");
+      ok(event.target.result instanceof IDBDatabase, "Expect a database here");
+
+      let db = event.target.result;
+      is(db.version, 1, "Database has correct version");
+
+      db.onupgradeneeded = function() {
+        ok(false, "our ongoing VERSION_CHANGE transaction should exclude any others!");
+      }
+
+      db.createObjectStore("foo");
+
+      try {
+        db.transaction("foo");
+        ok(false, "Transactions should be disallowed now!");
+      } catch (e) {
+        ok(e instanceof IDBDatabaseException, "Expect an IDBException");
+        is(e.code, IDBDatabaseException.NOT_ALLOWED_ERR, "Expect a NOT_ALLOWED_ERR");
+      }
+
+      request.transaction.oncomplete = grabEventAndContinueHandler;
+
+      yield;
+
+      // The database is still not fully open here.
+      try {
+        db.transaction("foo");
+        ok(false, "Transactions should be disallowed now!");
+      } catch (e) {
+        ok(e instanceof IDBDatabaseException, "Expect an IDBException");
+        is(e.code, IDBDatabaseException.NOT_ALLOWED_ERR, "Expect a NOT_ALLOWED_ERR");
+      }
+
+      request.onsuccess = grabEventAndContinueHandler;
+
+      yield;
+
+      db.onversionchange = function() {
+        ok(true, "next setVersion was unblocked appropriately");
+        db.close();
+      }
+
+      try {
+        db.transaction("foo");
+        ok(true, "Transactions should be allowed now!");
+      } catch (e) {
+        ok(false, "Transactions should be allowed now!");
+      }
+
+      request2.onupgradeneeded = null;
+
+      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_success_events_after_abort.html
+++ b/dom/indexedDB/test/test_success_events_after_abort.html
@@ -14,17 +14,17 @@
     {
       let request = mozIndexedDB.open(window.location.pathname, 1);
       request.onerror = errorHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
       let event = yield;
 
       let db = event.target.result;
 
-      event.target.transaction.oncomplete = continueToNextStep;
+      event.target.onsuccess = continueToNextStep;
 
       let objectStore = db.createObjectStore("foo");
       objectStore.add({}, 1).onerror = errorHandler;
 
       yield;
 
       objectStore = db.transaction("foo").objectStore("foo");
 
--- a/dom/indexedDB/test/test_transaction_abort.html
+++ b/dom/indexedDB/test/test_transaction_abort.html
@@ -24,16 +24,17 @@
 
       const name = window.location.pathname;
       const description = "My Test Database";
 
 
       let request = mozIndexedDB.open(name, 1, description);
       request.onerror = errorHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
+      request.onsuccess = grabEventAndContinueHandler;
       let event = yield;
 
       let db = event.target.result;
 
       let transaction;
       let objectStore;
 
       transaction = event.target.transaction;
@@ -152,16 +153,18 @@
       try {
         objectStore.deleteIndex("bar");
         ok(false, "Should have thrown");
       }
       catch (e) {
         ok(true, "RemoveIndex threw");
       }
 
+      yield;
+
       request = db.transaction("foo", READ_WRITE).objectStore("foo").add({});
       request.onerror = errorHandler;
       request.onsuccess = grabEventAndContinueHandler;
       event = yield;
 
       event.target.transaction.onabort = function(event) {
         ok(false, "Shouldn't see an abort event!");
       };
--- a/dom/indexedDB/test/test_transaction_lifetimes.html
+++ b/dom/indexedDB/test/test_transaction_lifetimes.html
@@ -15,17 +15,17 @@
       let request = mozIndexedDB.open(window.location.pathname, 1);
       request.onerror = errorHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
       let event = yield;
 
       let db = event.target.result;
       db.onerror = errorHandler;
 
-      event.target.transaction.oncomplete = continueToNextStep;
+      event.target.onsuccess = continueToNextStep;
 
       db.createObjectStore("foo", { autoIncrement: true });
       yield;
 
       let transaction = db.transaction("foo");
       continueToNextStep();
       yield;
 
--- a/dom/indexedDB/test/test_transaction_lifetimes_nested.html
+++ b/dom/indexedDB/test/test_transaction_lifetimes_nested.html
@@ -15,17 +15,17 @@
       let request = mozIndexedDB.open(window.location.pathname, 1);
       request.onerror = errorHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
       let event = yield;
 
       let db = event.target.result;
       db.onerror = errorHandler;
 
-      event.target.transaction.oncomplete = continueToNextStep;
+      event.target.onsuccess = continueToNextStep;
       db.createObjectStore("foo");
       yield;
 
       let transaction1 = db.transaction("foo");
       is(transaction1.readyState, IDBTransaction.INITIAL, "Correct readyState");
 
       let transaction2;
 
--- a/dom/indexedDB/test/test_writer_starvation.html
+++ b/dom/indexedDB/test/test_writer_starvation.html
@@ -18,33 +18,33 @@
         Components.interfaces.nsIIDBTransaction.VERSION_CHANGE;
 
       const name = window.location.pathname;
       const description = "My Test Database";
 
       let request = mozIndexedDB.open(name, 1, description);
       request.onerror = errorHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
+      request.onsuccess = grabEventAndContinueHandler;
       let event = yield;
 
       let db = event.target.result;
 
       is(event.target.transaction.mode, VERSION_CHANGE, "Correct mode");
 
       let objectStore = db.createObjectStore("foo", { autoIncrement: true });
 
       request = objectStore.add({});
       request.onerror = errorHandler;
       request.onsuccess = grabEventAndContinueHandler;
       event = yield;
 
       let key = event.target.result;
       ok(key, "Got a key");
 
-      SimpleTest.executeSoon(function() { testGenerator.next(); });
       yield;
 
       let continueReading = true;
       let readerCount = 0;
       let callbackCount = 0;
       let finalCallbackCount = 0;
 
       // Generate a bunch of reads right away without returning to the event