Bug 1149815 - Part 4: Properly handle IDBDatabase.close() called during a versionchange transaction, v1
authorJames Graham <james@hoppipolla.co.uk>
Wed, 17 Jun 2015 10:45:47 +0100
changeset 326823 983652ea25803da1f68f2edcca038d477cb28a49
parent 326822 0325e7cdfb170791946c30188c7f26b251f4c197
child 326824 6e4509a7a36b1dca2d272f044fe6e3d978d4d1f0
push id10169
push userdminor@mozilla.com
push dateThu, 28 Jan 2016 13:10:48 +0000
bugs1149815
milestone41.0a1
Bug 1149815 - Part 4: Properly handle IDBDatabase.close() called during a versionchange transaction, v1
dom/indexedDB/ActorsChild.cpp
dom/indexedDB/ActorsParent.cpp
dom/indexedDB/test/unit/test_cursor_update_updates_indexes.js
dom/indexedDB/test/unit/test_objectCursors.js
dom/indexedDB/test/unit/test_objectStore_remove_values.js
dom/indexedDB/test/unit/test_remove_objectStore.js
dom/indexedDB/test/unit/test_setVersion.js
--- a/dom/indexedDB/ActorsChild.cpp
+++ b/dom/indexedDB/ActorsChild.cpp
@@ -1257,24 +1257,36 @@ BackgroundFactoryRequestChild::HandleRes
   AssertIsOnOwningThread();
 
   mRequest->Reset();
 
   auto databaseActor =
     static_cast<BackgroundDatabaseChild*>(aResponse.databaseChild());
   MOZ_ASSERT(databaseActor);
 
-  databaseActor->EnsureDOMObject();
-
   IDBDatabase* database = databaseActor->GetDOMObject();
-  MOZ_ASSERT(database);
-
-  ResultHelper helper(mRequest, nullptr, database);
-
-  DispatchSuccessEvent(&helper);
+  if (!database) {
+    databaseActor->EnsureDOMObject();
+
+    database = databaseActor->GetDOMObject();
+    MOZ_ASSERT(database);
+
+    MOZ_ASSERT(!database->IsClosed());
+  }
+
+  if (database->IsClosed()) {
+    // If the database was closed already, which is only possible if we fired an
+    // "upgradeneeded" event, then we shouldn't fire a "success" event here.
+    // Instead we fire an error event with AbortErr.
+    DispatchErrorEvent(mRequest, NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
+  } else {
+    ResultHelper helper(mRequest, nullptr, database);
+
+    DispatchSuccessEvent(&helper);
+  }
 
   databaseActor->ReleaseDOMObject();
 
   return true;
 }
 
 bool
 BackgroundFactoryRequestChild::HandleResponse(
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -19685,17 +19685,28 @@ OpenDatabaseOp::BeginVersionChange()
   mState = State_WaitingForOtherDatabasesToClose;
   return NS_OK;
 }
 
 void
 OpenDatabaseOp::NoteDatabaseClosed(Database* aDatabase)
 {
   AssertIsOnOwningThread();
-  MOZ_ASSERT(mState == State_WaitingForOtherDatabasesToClose);
+  MOZ_ASSERT(aDatabase);
+  MOZ_ASSERT(mState == State_WaitingForOtherDatabasesToClose ||
+             mState == State_DatabaseWorkVersionChange);
+
+  if (mState == State_DatabaseWorkVersionChange) {
+    MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
+    MOZ_ASSERT(mRequestedVersion >
+                 aDatabase->Metadata()->mCommonMetadata.version(),
+               "Must only be closing databases for a previous version!");
+    return;
+  }
+
   MOZ_ASSERT(!mMaybeBlockedDatabases.IsEmpty());
 
   bool actorDestroyed = IsActorDestroyed() || mDatabase->IsActorDestroyed();
 
   nsresult rv;
   if (actorDestroyed) {
     IDB_REPORT_INTERNAL_ERR();
     rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
--- a/dom/indexedDB/test/unit/test_cursor_update_updates_indexes.js
+++ b/dom/indexedDB/test/unit/test_cursor_update_updates_indexes.js
@@ -27,16 +27,19 @@ function testSteps()
 
     ok(true, "1");
     request = indexedDB.open(name, i + 1);
     request.onerror = errorHandler;
     request.onupgradeneeded = grabEventAndContinueHandler;
     event = yield undefined;
 
     let db = event.target.result;
+    db.onversionchange = function(event) {
+      event.target.close();
+    };
 
     ok(true, "2");
     let objectStore = info.hasOwnProperty("options") ?
                       db.createObjectStore(info.name, info.options) :
                       db.createObjectStore(info.name);
 
     // Create the indexes on 'data' on the object store.
     let index = objectStore.createIndex("data_index", "data",
@@ -80,15 +83,14 @@ function testSteps()
     request = uniqueIndex.get(END_DATA);
     request.onerror = errorHandler;
     request.onsuccess = grabEventAndContinueHandler;
     event = yield undefined;
 
     ok(true, "7");
     ok(obj.data, event.target.result.data,
                   "Unique index was properly updated.");
-    db.close();
   }
 
   finishTest();
   yield undefined;
 }
 
--- a/dom/indexedDB/test/unit/test_objectCursors.js
+++ b/dom/indexedDB/test/unit/test_objectCursors.js
@@ -22,16 +22,19 @@ function testSteps()
   var j = 0;
   for (let i in objectStores) {
     let request = indexedDB.open(name, ++j);
     request.onerror = errorHandler;
     request.onupgradeneeded = grabEventAndContinueHandler;
     let event = yield undefined;
 
     let db = event.target.result;
+    db.onversionchange = function(event) {
+      event.target.close();
+    };
 
     let objectStore =
       db.createObjectStore(objectStores[i].name,
                            { keyPath: "id",
                              autoIncrement: objectStores[i].autoIncrement });
 
     for (let j in indexes) {
       objectStore.createIndex(indexes[j].name, "name", indexes[j].options);
@@ -43,17 +46,16 @@ function testSteps()
     }
 
     request = objectStore.add(data);
     request.onerror = errorHandler;
     request.onsuccess = grabEventAndContinueHandler;
     event = yield undefined;
 
     ok(event.target.result == 1 || event.target.result == 2, "Good id");
-    db.close();
   }
 
   executeSoon(function() { testGenerator.next(); });
   yield undefined;
 
   let request = indexedDB.open(name, j);
   request.onerror = errorHandler;
   request.onsuccess = grabEventAndContinueHandler;
--- a/dom/indexedDB/test/unit/test_objectStore_remove_values.js
+++ b/dom/indexedDB/test/unit/test_objectStore_remove_values.js
@@ -40,16 +40,19 @@ function testSteps()
     let test = data[i];
 
     let request = indexedDB.open(name, i+1);
     request.onerror = errorHandler;
     request.onupgradeneeded = grabEventAndContinueHandler;
     let event = yield undefined;
 
     let db = event.target.result;
+    db.onversionchange = function(event) {
+      event.target.close();
+    };
 
     let objectStore = db.createObjectStore(test.name,
                                            { keyPath: test.keyName,
                                              autoIncrement: test.autoIncrement });
 
     request = objectStore.add(test.storedObject, test.keyValue);
     request.onerror = errorHandler;
     request.onsuccess = grabEventAndContinueHandler;
@@ -72,15 +75,14 @@ function testSteps()
 
     // Make sure it was removed.
     request = objectStore.get(id);
     request.onerror = errorHandler;
     request.onsuccess = grabEventAndContinueHandler;
     event = yield undefined;
 
     ok(event.target.result === undefined, "Object was deleted");
-    db.close();
   }
 
   finishTest();
   yield undefined;
 }
 
--- a/dom/indexedDB/test/unit/test_remove_objectStore.js
+++ b/dom/indexedDB/test/unit/test_remove_objectStore.js
@@ -8,18 +8,22 @@ var testGenerator = testSteps();
 function testSteps()
 {
   const name = this.window ? window.location.pathname : "Splendid Test";
   const objectStoreName = "Objects";
 
   let request = indexedDB.open(name, 1);
   request.onerror = errorHandler;
   request.onupgradeneeded = grabEventAndContinueHandler;
+  request.onsuccess = unexpectedSuccessHandler;
   let event = yield undefined;
 
+  request.onupgradeneeded = unexpectedSuccessHandler;
+  request.onsuccess = grabEventAndContinueHandler;
+
   let db = event.target.result;
   is(db.objectStoreNames.length, 0, "Correct objectStoreNames list");
 
   let objectStore = db.createObjectStore(objectStoreName,
                                          { keyPath: "foo" });
 
   let addedCount = 0;
 
@@ -35,23 +39,30 @@ function testSteps()
   yield undefined;
 
   is(db.objectStoreNames.length, 1, "Correct objectStoreNames list");
   is(db.objectStoreNames.item(0), objectStoreName, "Correct name");
 
   event.target.transaction.oncomplete = grabEventAndContinueHandler;
   event = yield undefined;
 
+  // Wait for success.
+  event = yield undefined;
+
   db.close();
 
   request = indexedDB.open(name, 2);
   request.onerror = errorHandler;
   request.onupgradeneeded = grabEventAndContinueHandler;
+  request.onsuccess = unexpectedSuccessHandler;
   event = yield undefined;
 
+  request.onupgradeneeded = unexpectedSuccessHandler;
+  request.onsuccess = grabEventAndContinueHandler;
+
   db = event.target.result;
   let trans = event.target.transaction;
 
   let oldObjectStore = trans.objectStore(objectStoreName);
   isnot(oldObjectStore, null, "Correct object store prior to deleting");
   db.deleteObjectStore(objectStoreName);
   is(db.objectStoreNames.length, 0, "Correct objectStores list");
   try {
@@ -82,16 +93,19 @@ function testSteps()
   is(db.objectStoreNames.length, 0, "Correct objectStores list");
 
   continueToNextStep();
   yield undefined;
 
   trans.oncomplete = grabEventAndContinueHandler;
   event = yield undefined;
 
+  // Wait for success.
+  event = yield undefined;
+
   db.close();
 
   request = indexedDB.open(name, 3);
   request.onerror = errorHandler;
   request.onupgradeneeded = grabEventAndContinueHandler;
   event = yield undefined;
 
   db = event.target.result;
--- a/dom/indexedDB/test/unit/test_setVersion.js
+++ b/dom/indexedDB/test/unit/test_setVersion.js
@@ -10,41 +10,43 @@ function testSteps()
   const name = this.window ? window.location.pathname : "Splendid Test";
 
   let request = indexedDB.open(name, 1);
   request.onerror = errorHandler;
   request.onsuccess = grabEventAndContinueHandler;
   let event = yield undefined;
 
   let db = event.target.result;
+  db.onversionchange = function() {
+    db.close();
+  };
 
   // Check default state.
   is(db.version, 1, "Correct default version for a new database.");
 
   const versions = [
     7,
     42,
   ];
 
-  db.close();
+  
 
   for (let i = 0; i < versions.length; i++) {
     let version = versions[i];
 
     let request = indexedDB.open(name, version);
     request.onerror = errorHandler;
     request.onupgradeneeded = grabEventAndContinueHandler;
     let event = yield undefined;
 
     let db = event.target.result;
+    db.onversionchange = function(event) {
+      event.target.close();
+    };
 
     is(db.version, version, "Database version number updated correctly");
     is(event.target.transaction.mode, "versionchange", "Correct mode");
-
-    executeSoon(function() { testGenerator.next(); });
-    yield undefined;
-    db.close();
   }
 
   finishTest();
   yield undefined;
 }