Modern IDB: Support IDBDatabase.transaction() (and transaction scheduling in general).
authorbeidson@apple.com <beidson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 29 Oct 2015 06:16:01 +0000 (06:16 +0000)
committerbeidson@apple.com <beidson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 29 Oct 2015 06:16:01 +0000 (06:16 +0000)
https://bugs.webkit.org/show_bug.cgi?id=150614

Reviewed by Alex Christensen.

LayoutTests/imported/w3c:

* indexeddb/idbdatabase_transaction5-expected.txt: Progression!

Source/WebCore:

Tests: storage/indexeddb/modern/idbdatabase-transaction-failures.html
       storage/indexeddb/modern/transaction-scheduler-1.html
       storage/indexeddb/modern/transaction-scheduler-2.html
       storage/indexeddb/modern/transaction-scheduler-3.html
       storage/indexeddb/modern/transaction-scheduler-4.html
       storage/indexeddb/modern/transaction-scheduler-5.html
       storage/indexeddb/modern/transaction-scheduler-6.html

* Modules/indexeddb/IDBDatabase.idl:

* Modules/indexeddb/IndexedDB.h:

* Modules/indexeddb/client/IDBConnectionToServer.cpp:
(WebCore::IDBClient::IDBConnectionToServer::establishTransaction):
(WebCore::IDBClient::IDBConnectionToServer::didStartTransaction):
(WebCore::IDBClient::IDBConnectionToServer::hasRecordOfTransaction):
* Modules/indexeddb/client/IDBConnectionToServer.h:
* Modules/indexeddb/client/IDBConnectionToServerDelegate.h:

* Modules/indexeddb/client/IDBDatabaseImpl.cpp:
(WebCore::IDBClient::IDBDatabase::transaction):
(WebCore::IDBClient::IDBDatabase::didStartTransaction):
* Modules/indexeddb/client/IDBDatabaseImpl.h:

* Modules/indexeddb/client/IDBTransactionImpl.cpp:
(WebCore::IDBClient::IDBTransaction::IDBTransaction):
(WebCore::IDBClient::IDBTransaction::operationTimerFired):
(WebCore::IDBClient::IDBTransaction::didStart):
(WebCore::IDBClient::IDBTransaction::establishOnServer):
(WebCore::IDBClient::IDBTransaction::activate):
(WebCore::IDBClient::IDBTransaction::deactivate):
* Modules/indexeddb/client/IDBTransactionImpl.h:

* Modules/indexeddb/server/IDBConnectionToClient.cpp:
(WebCore::IDBServer::IDBConnectionToClient::didStartTransaction):
* Modules/indexeddb/server/IDBConnectionToClient.h:
* Modules/indexeddb/server/IDBConnectionToClientDelegate.h:

* Modules/indexeddb/server/IDBServer.cpp:
(WebCore::IDBServer::IDBServer::establishTransaction):
* Modules/indexeddb/server/IDBServer.h:

* Modules/indexeddb/server/MemoryIDBBackingStore.cpp:
(WebCore::IDBServer::MemoryIDBBackingStore::beginTransaction):
(WebCore::IDBServer::MemoryIDBBackingStore::createObjectStore):
(WebCore::IDBServer::MemoryIDBBackingStore::removeObjectStoreForVersionChangeAbort):
(WebCore::IDBServer::MemoryIDBBackingStore::keyExistsInObjectStore):
(WebCore::IDBServer::MemoryIDBBackingStore::deleteRecord):
(WebCore::IDBServer::MemoryIDBBackingStore::putRecord):
(WebCore::IDBServer::MemoryIDBBackingStore::getRecord):
(WebCore::IDBServer::MemoryIDBBackingStore::registerObjectStore):
(WebCore::IDBServer::MemoryIDBBackingStore::unregisterObjectStore):
* Modules/indexeddb/server/MemoryIDBBackingStore.h:

* Modules/indexeddb/server/UniqueIDBDatabase.cpp:
(WebCore::IDBServer::UniqueIDBDatabase::startVersionChangeTransaction):
(WebCore::IDBServer::UniqueIDBDatabase::performCommitTransaction):
(WebCore::IDBServer::UniqueIDBDatabase::didPerformCommitTransaction):
(WebCore::IDBServer::UniqueIDBDatabase::didPerformAbortTransaction):
(WebCore::IDBServer::UniqueIDBDatabase::enqueueTransaction):
(WebCore::IDBServer::UniqueIDBDatabase::transactionSchedulingTimerFired):
(WebCore::IDBServer::UniqueIDBDatabase::activateTransactionInBackingStore):
(WebCore::IDBServer::UniqueIDBDatabase::performActivateTransactionInBackingStore):
(WebCore::IDBServer::UniqueIDBDatabase::didPerformActivateTransactionInBackingStore):
(WebCore::IDBServer::scopesOverlap):
(WebCore::IDBServer::UniqueIDBDatabase::takeNextRunnableTransaction):
(WebCore::IDBServer::UniqueIDBDatabase::inProgressTransactionCompleted):
* Modules/indexeddb/server/UniqueIDBDatabase.h:

* Modules/indexeddb/server/UniqueIDBDatabaseConnection.cpp:
(WebCore::IDBServer::UniqueIDBDatabaseConnection::establishTransaction):
* Modules/indexeddb/server/UniqueIDBDatabaseConnection.h:

* Modules/indexeddb/server/UniqueIDBDatabaseTransaction.cpp:
(WebCore::IDBServer::UniqueIDBDatabaseTransaction::create):
(WebCore::IDBServer::UniqueIDBDatabaseTransaction::UniqueIDBDatabaseTransaction):
(WebCore::IDBServer::UniqueIDBDatabaseTransaction::objectStoreIdentifiers):
(WebCore::IDBServer::UniqueIDBDatabaseTransaction::didActivateInBackingStore):
* Modules/indexeddb/server/UniqueIDBDatabaseTransaction.h:

* Modules/indexeddb/shared/IDBTransactionInfo.cpp:
(WebCore::IDBTransactionInfo::clientTransaction):
(WebCore::IDBTransactionInfo::isolatedCopy):
* Modules/indexeddb/shared/IDBTransactionInfo.h:

* Modules/indexeddb/shared/InProcessIDBServer.cpp:
(WebCore::InProcessIDBServer::establishTransaction):
(WebCore::InProcessIDBServer::didStartTransaction):
* Modules/indexeddb/shared/InProcessIDBServer.h:

* bindings/js/IDBBindingUtilities.cpp:
(WebCore::deserializeIDBValueData):

* bindings/js/JSIDBDatabaseCustom.cpp:
(WebCore::JSIDBDatabase::transaction):

* bindings/js/ScriptState.cpp:
(WebCore::execStateFromPage):

LayoutTests:

* storage/indexeddb/modern/idbdatabase-transaction-failures-expected.txt: Added.
* storage/indexeddb/modern/idbdatabase-transaction-failures.html: Added.
* storage/indexeddb/modern/transaction-scheduler-1-expected.txt: Added.
* storage/indexeddb/modern/transaction-scheduler-1.html: Added.
* storage/indexeddb/modern/transaction-scheduler-2-expected.txt: Added.
* storage/indexeddb/modern/transaction-scheduler-2.html: Added.
* storage/indexeddb/modern/transaction-scheduler-3-expected.txt: Added.
* storage/indexeddb/modern/transaction-scheduler-3.html: Added.
* storage/indexeddb/modern/transaction-scheduler-4-expected.txt: Added.
* storage/indexeddb/modern/transaction-scheduler-4.html: Added.
* storage/indexeddb/modern/transaction-scheduler-5-expected.txt: Added.
* storage/indexeddb/modern/transaction-scheduler-5.html: Added.
* storage/indexeddb/modern/transaction-scheduler-6-expected.txt: Added.
* storage/indexeddb/modern/transaction-scheduler-6.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@191722 268f45cc-cd09-0410-ab3c-d52691b4dbfc

47 files changed:
LayoutTests/ChangeLog
LayoutTests/imported/w3c/ChangeLog
LayoutTests/imported/w3c/indexeddb/idbdatabase_transaction5-expected.txt
LayoutTests/storage/indexeddb/modern/idbdatabase-transaction-failures-expected.txt [new file with mode: 0644]
LayoutTests/storage/indexeddb/modern/idbdatabase-transaction-failures.html [new file with mode: 0644]
LayoutTests/storage/indexeddb/modern/transaction-scheduler-1-expected.txt [new file with mode: 0644]
LayoutTests/storage/indexeddb/modern/transaction-scheduler-1.html [new file with mode: 0644]
LayoutTests/storage/indexeddb/modern/transaction-scheduler-2-expected.txt [new file with mode: 0644]
LayoutTests/storage/indexeddb/modern/transaction-scheduler-2.html [new file with mode: 0644]
LayoutTests/storage/indexeddb/modern/transaction-scheduler-3-expected.txt [new file with mode: 0644]
LayoutTests/storage/indexeddb/modern/transaction-scheduler-3.html [new file with mode: 0644]
LayoutTests/storage/indexeddb/modern/transaction-scheduler-4-expected.txt [new file with mode: 0644]
LayoutTests/storage/indexeddb/modern/transaction-scheduler-4.html [new file with mode: 0644]
LayoutTests/storage/indexeddb/modern/transaction-scheduler-5-expected.txt [new file with mode: 0644]
LayoutTests/storage/indexeddb/modern/transaction-scheduler-5.html [new file with mode: 0644]
LayoutTests/storage/indexeddb/modern/transaction-scheduler-6-expected.txt [new file with mode: 0644]
LayoutTests/storage/indexeddb/modern/transaction-scheduler-6.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/Modules/indexeddb/IDBDatabase.idl
Source/WebCore/Modules/indexeddb/IndexedDB.h
Source/WebCore/Modules/indexeddb/client/IDBConnectionToServer.cpp
Source/WebCore/Modules/indexeddb/client/IDBConnectionToServer.h
Source/WebCore/Modules/indexeddb/client/IDBConnectionToServerDelegate.h
Source/WebCore/Modules/indexeddb/client/IDBDatabaseImpl.cpp
Source/WebCore/Modules/indexeddb/client/IDBDatabaseImpl.h
Source/WebCore/Modules/indexeddb/client/IDBTransactionImpl.cpp
Source/WebCore/Modules/indexeddb/client/IDBTransactionImpl.h
Source/WebCore/Modules/indexeddb/server/IDBConnectionToClient.cpp
Source/WebCore/Modules/indexeddb/server/IDBConnectionToClient.h
Source/WebCore/Modules/indexeddb/server/IDBConnectionToClientDelegate.h
Source/WebCore/Modules/indexeddb/server/IDBServer.cpp
Source/WebCore/Modules/indexeddb/server/IDBServer.h
Source/WebCore/Modules/indexeddb/server/MemoryIDBBackingStore.cpp
Source/WebCore/Modules/indexeddb/server/MemoryIDBBackingStore.h
Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.cpp
Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.h
Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabaseConnection.cpp
Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabaseConnection.h
Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabaseTransaction.cpp
Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabaseTransaction.h
Source/WebCore/Modules/indexeddb/shared/IDBTransactionInfo.cpp
Source/WebCore/Modules/indexeddb/shared/IDBTransactionInfo.h
Source/WebCore/Modules/indexeddb/shared/InProcessIDBServer.cpp
Source/WebCore/Modules/indexeddb/shared/InProcessIDBServer.h
Source/WebCore/bindings/js/IDBBindingUtilities.cpp
Source/WebCore/bindings/js/JSIDBDatabaseCustom.cpp
Source/WebCore/bindings/js/ScriptState.cpp

index 3e28526..376f764 100644 (file)
@@ -1,3 +1,25 @@
+2015-10-28  Brady Eidson  <beidson@apple.com>
+
+        Modern IDB: Support IDBDatabase.transaction() (and transaction scheduling in general).
+        https://bugs.webkit.org/show_bug.cgi?id=150614
+
+        Reviewed by Alex Christensen.
+
+        * storage/indexeddb/modern/idbdatabase-transaction-failures-expected.txt: Added.
+        * storage/indexeddb/modern/idbdatabase-transaction-failures.html: Added.
+        * storage/indexeddb/modern/transaction-scheduler-1-expected.txt: Added.
+        * storage/indexeddb/modern/transaction-scheduler-1.html: Added.
+        * storage/indexeddb/modern/transaction-scheduler-2-expected.txt: Added.
+        * storage/indexeddb/modern/transaction-scheduler-2.html: Added.
+        * storage/indexeddb/modern/transaction-scheduler-3-expected.txt: Added.
+        * storage/indexeddb/modern/transaction-scheduler-3.html: Added.
+        * storage/indexeddb/modern/transaction-scheduler-4-expected.txt: Added.
+        * storage/indexeddb/modern/transaction-scheduler-4.html: Added.
+        * storage/indexeddb/modern/transaction-scheduler-5-expected.txt: Added.
+        * storage/indexeddb/modern/transaction-scheduler-5.html: Added.
+        * storage/indexeddb/modern/transaction-scheduler-6-expected.txt: Added.
+        * storage/indexeddb/modern/transaction-scheduler-6.html: Added.
+
 2015-10-28  Andy Estes  <aestes@apple.com>
 
         [Content Filtering] Crash when allowing a 0-byte resource to load
index f9bb2bf..c58d070 100644 (file)
@@ -1,3 +1,12 @@
+2015-10-28  Brady Eidson  <beidson@apple.com>
+
+        Modern IDB: Support IDBDatabase.transaction() (and transaction scheduling in general).
+        https://bugs.webkit.org/show_bug.cgi?id=150614
+
+        Reviewed by Alex Christensen.
+
+        * indexeddb/idbdatabase_transaction5-expected.txt: Progression!
+
 2015-10-26  Chris Dumez  <cdumez@apple.com>
 
         Indexing an object with an integer that is not a supported property index should not call the named property getter
index 8d0c2ae..41c7283 100644 (file)
@@ -1,3 +1,3 @@
 
-FAIL IDBDatabase.transaction() - If storeNames is an empty list, the implementation must throw a DOMException of type InvalidAccessError assert_throws: function "function () { db.transaction([]); }" threw object "Error: NotFoundError: DOM IDBDatabase Exception 8" that is not a DOMException InvalidAccessError: property "code" is equal to 8, expected 15
+PASS IDBDatabase.transaction() - If storeNames is an empty list, the implementation must throw a DOMException of type InvalidAccessError 
 
diff --git a/LayoutTests/storage/indexeddb/modern/idbdatabase-transaction-failures-expected.txt b/LayoutTests/storage/indexeddb/modern/idbdatabase-transaction-failures-expected.txt
new file mode 100644 (file)
index 0000000..2cede3a
--- /dev/null
@@ -0,0 +1,10 @@
+ALERT: Upgrade needed: Old version - 0 New version - 1
+ALERT: Failed to start a transaction while a versionChange transaction was in progress - Error: InvalidStateError: DOM Exception 11
+ALERT: versionchange transaction completed
+ALERT: Failed to start a transaction with an empty set of object stores - Error: InvalidAccessError: DOM Exception 15
+ALERT: Failed to start a transaction to a nonexistent object store - Error: NotFoundError: DOM Exception 8
+ALERT: Failed to start a transaction with an invalid mode - TypeError: Type error
+ALERT: Failed to explicitly start a versionchange transaction - TypeError: Type error
+ALERT: Failed to explicitly start a transaction with the close pending flag set - Error: InvalidStateError: DOM Exception 11
+ALERT: Done
+This tests some obvious failures that can happen while calling IDBDatabase.transaction()
diff --git a/LayoutTests/storage/indexeddb/modern/idbdatabase-transaction-failures.html b/LayoutTests/storage/indexeddb/modern/idbdatabase-transaction-failures.html
new file mode 100644 (file)
index 0000000..68b7784
--- /dev/null
@@ -0,0 +1,90 @@
+This tests some obvious failures that can happen while calling IDBDatabase.transaction()
+<script>
+
+if (window.testRunner) {
+    testRunner.waitUntilDone();
+    testRunner.dumpAsText();
+}
+
+function done()
+{
+    alert("Done");
+    if (window.testRunner)
+        testRunner.notifyDone();
+}
+
+var createRequest = window.indexedDB.open("IDBDatabaseTransactionFailuresDatabase");
+var database;
+
+createRequest.onupgradeneeded = function(event) {
+    alert("Upgrade needed: Old version - " + event.oldVersion + " New version - " + event.newVersion);
+
+    var versionTransaction = createRequest.transaction;
+    database = event.target.result;
+    var objectStore = database.createObjectStore("TestObjectStore");
+    var request = objectStore.put("foo", "bar");
+
+    request.onerror = function(event) {
+        alert("put FAILED - " + event);
+        done();
+    }
+    
+    try {
+        database.transaction("TestObjectStore", "readonly");
+    } catch(e) {
+        alert("Failed to start a transaction while a versionChange transaction was in progress - " + e);
+    }
+
+    versionTransaction.onabort = function(event) {
+        alert("versionchange transaction aborted");
+        done();
+    }
+
+    versionTransaction.oncomplete = function(event) {
+        alert("versionchange transaction completed");
+        continueTest();
+    }
+
+    versionTransaction.onerror = function(event) {
+        alert("versionchange transaction error'ed - " + event);
+        done();
+    }
+}
+
+function continueTest()
+{
+    try {
+        database.transaction([], "readonly");
+    } catch(e) {
+        alert("Failed to start a transaction with an empty set of object stores - " + e);
+    }
+
+    try {
+        database.transaction("NonexistentObjectStore", "readonly");
+    } catch(e) {
+        alert("Failed to start a transaction to a nonexistent object store - " + e);
+    }
+
+    try {
+        database.transaction("TestObjectStore", "blahblah");
+    } catch(e) {
+        alert("Failed to start a transaction with an invalid mode - " + e);
+    }
+
+    try {
+        database.transaction("TestObjectStore", "versionchange");
+    } catch(e) {
+        alert("Failed to explicitly start a versionchange transaction - " + e);
+    }
+    
+    try {
+        database.close();
+        database.transaction("TestObjectStore", "readonly");
+    } catch(e) {
+        alert("Failed to explicitly start a transaction with the close pending flag set - " + e);
+    }
+    
+    done();
+}
+
+</script>
diff --git a/LayoutTests/storage/indexeddb/modern/transaction-scheduler-1-expected.txt b/LayoutTests/storage/indexeddb/modern/transaction-scheduler-1-expected.txt
new file mode 100644 (file)
index 0000000..24571ad
--- /dev/null
@@ -0,0 +1,7 @@
+ALERT: Upgrade needed: Old version - 0 New version - 1
+ALERT: versionchange transaction completed
+ALERT: Success opening database connection - request 1
+ALERT: Success opening database connection - request 2
+ALERT: Two transactions open at once. Yay.
+ALERT: Done
+This test makes sure that two read-only transactions to an object store are active at the same time.
diff --git a/LayoutTests/storage/indexeddb/modern/transaction-scheduler-1.html b/LayoutTests/storage/indexeddb/modern/transaction-scheduler-1.html
new file mode 100644 (file)
index 0000000..b0c3d7e
--- /dev/null
@@ -0,0 +1,120 @@
+This test makes sure that two read-only transactions to an object store are active at the same time.
+<script>
+
+if (window.testRunner) {
+    testRunner.waitUntilDone();
+    testRunner.dumpAsText();
+}
+
+function done()
+{
+    alert("Done");
+    if (window.testRunner)
+        testRunner.notifyDone();
+}
+
+var createRequest = window.indexedDB.open("TransactionScheduler1Database");
+
+createRequest.onupgradeneeded = function(event) {
+    alert("Upgrade needed: Old version - " + event.oldVersion + " New version - " + event.newVersion);
+
+    var versionTransaction = createRequest.transaction;
+    var database = event.target.result;
+    var objectStore = database.createObjectStore("TestObjectStore");
+    var request = objectStore.put("foo", "bar");
+
+    request.onerror = function(event) {
+        alert("put FAILED - " + event);
+        done();
+    }
+    
+    versionTransaction.onabort = function(event) {
+        alert("versionchange transaction aborted");
+        done();
+    }
+
+    versionTransaction.oncomplete = function(event) {
+        alert("versionchange transaction completed");
+        continueTest();
+        database.close();
+    }
+
+    versionTransaction.onerror = function(event) {
+        alert("versionchange transaction error'ed - " + event);
+        done();
+    }
+}
+
+function continueTest()
+{
+    var request1 = window.indexedDB.open("TransactionScheduler1Database", 1);
+    var request2 = window.indexedDB.open("TransactionScheduler1Database", 1);
+
+    setupRequest(request1, "request 1");
+    setupRequest(request2, "request 2");
+}
+
+function setupRequest(request, reqName)
+{
+    request.onsuccess = function(event) {
+        alert("Success opening database connection - " + reqName);
+        var database = event.target.result;
+    
+        startTransactionLoop(event.target.result.transaction("TestObjectStore", "readonly"), true);
+    }
+    request.onerror = function(event) {
+        alert("Unexpected error - " + reqName + " - " + event);
+        done();
+    }
+    request.onblocked = function(event) {
+        alert("Unexpected blocked - " + reqName + " - " + event);
+        done();
+    }
+    request.onupgradeneeded = function(event) {
+        alert("Unexpected upgradeneeded - " + reqName + " - " + event);
+        done();
+    } 
+}
+
+var numberOfOpenTransactions = 0;
+
+function startTransactionLoop(transaction, isFirstTime)
+{
+    var objectStore = transaction.objectStore("TestObjectStore");
+    var request = objectStore.get("bar");
+    
+    request.onsuccess = function(event) {
+        if (isFirstTime)
+            numberOfOpenTransactions++;
+        
+        if (numberOfOpenTransactions == 2) {
+            alert("Two transactions open at once. Yay.");
+            done();
+        }
+        startTransactionLoop(event.target.transaction, false);
+    }
+
+    request.onerror = function(event) {
+        alert("Unexpected request error - " + event);
+        done();
+    }
+
+    transaction.onerror = function(event) {
+        alert("Unexpected transaction error - " + event);
+        done();
+    }
+
+    transaction.onabort = function(event) {
+        --numberOfOpenTransactions;
+        alert("Unexpected transaction abort - " + event);
+        done();
+    }
+
+    transaction.oncomplete = function(event) {
+        --numberOfOpenTransactions;
+        alert("Unexpected transaction complete - " + event);
+        done();
+    }
+}
+
+</script>
diff --git a/LayoutTests/storage/indexeddb/modern/transaction-scheduler-2-expected.txt b/LayoutTests/storage/indexeddb/modern/transaction-scheduler-2-expected.txt
new file mode 100644 (file)
index 0000000..dd13fae
--- /dev/null
@@ -0,0 +1,7 @@
+ALERT: Upgrade needed: Old version - 0 New version - 1
+ALERT: versionchange transaction completed
+ALERT: Success opening database connection - Starting transaction to ObjectStore OS1
+ALERT: Success opening database connection - Starting transaction to ObjectStore OS2
+ALERT: Two transactions open at once. Yay.
+ALERT: Done
+This test makes sure that two read-only transactions to two different object stores are active at the same time.
diff --git a/LayoutTests/storage/indexeddb/modern/transaction-scheduler-2.html b/LayoutTests/storage/indexeddb/modern/transaction-scheduler-2.html
new file mode 100644 (file)
index 0000000..182a50e
--- /dev/null
@@ -0,0 +1,126 @@
+This test makes sure that two read-only transactions to two different object stores are active at the same time.
+<script>
+
+if (window.testRunner) {
+    testRunner.waitUntilDone();
+    testRunner.dumpAsText();
+}
+
+function done()
+{
+    alert("Done");
+    if (window.testRunner)
+        testRunner.notifyDone();
+}
+
+var createRequest = window.indexedDB.open("TransactionScheduler2Database");
+
+createRequest.onupgradeneeded = function(event) {
+    alert("Upgrade needed: Old version - " + event.oldVersion + " New version - " + event.newVersion);
+
+    var versionTransaction = createRequest.transaction;
+    var database = event.target.result;
+    var objectStore = database.createObjectStore("OS1");
+    var request = objectStore.put("foo1", "bar1");
+
+    request.onerror = function(event) {
+        alert("put1 FAILED - " + event);
+        done();
+    }
+    
+    objectStore = database.createObjectStore("OS2");
+    request = objectStore.put("foo2", "bar2");
+
+    request.onerror = function(event) {
+        alert("put2 FAILED - " + event);
+        done();
+    }
+
+    versionTransaction.onabort = function(event) {
+        alert("versionchange transaction aborted");
+        done();
+    }
+
+    versionTransaction.oncomplete = function(event) {
+        alert("versionchange transaction completed");
+        continueTest();
+        database.close();
+    }
+
+    versionTransaction.onerror = function(event) {
+        alert("versionchange transaction error'ed - " + event);
+        done();
+    }
+}
+
+function continueTest()
+{
+    var request1 = window.indexedDB.open("TransactionScheduler2Database", 1);
+    var request2 = window.indexedDB.open("TransactionScheduler2Database", 1);
+
+    setupRequest(request1, "OS1");
+    setupRequest(request2, "OS2");
+}
+
+function setupRequest(request, osname)
+{
+    request.onsuccess = function(event) {
+        alert("Success opening database connection - Starting transaction to ObjectStore " + osname);
+        startTransactionLoop(event.target.result.transaction(osname, "readonly"), osname, true);
+    }
+    request.onerror = function(event) {
+        alert("Unexpected error - " + osname + " - " + event);
+        done();
+    }
+    request.onblocked = function(event) {
+        alert("Unexpected blocked - " + osname + " - " + event);
+        done();
+    }
+    request.onupgradeneeded = function(event) {
+        alert("Unexpected upgradeneeded - " + osname + " - " + event);
+        done();
+    } 
+}
+
+var numberOfOpenTransactions = 0;
+
+function startTransactionLoop(transaction, osname, isFirstTime)
+{
+    var objectStore = transaction.objectStore(osname);
+    var request = objectStore.get("bar");
+    
+    request.onsuccess = function(event) {
+        if (isFirstTime)
+            numberOfOpenTransactions++;
+        
+        if (numberOfOpenTransactions == 2) {
+            alert("Two transactions open at once. Yay.");
+            done();
+        }
+        startTransactionLoop(event.target.transaction, osname, false);
+    }
+
+    request.onerror = function(event) {
+        alert("Unexpected request error - " + event);
+        done();
+    }
+
+    transaction.onerror = function(event) {
+        alert("Unexpected transaction error - " + event);
+        done();
+    }
+
+    transaction.onabort = function(event) {
+        --numberOfOpenTransactions;
+        alert("Unexpected transaction abort - " + event);
+        done();
+    }
+
+    transaction.oncomplete = function(event) {
+        --numberOfOpenTransactions;
+        alert("Unexpected transaction complete - " + event);
+        done();
+    }
+}
+
+</script>
\ No newline at end of file
diff --git a/LayoutTests/storage/indexeddb/modern/transaction-scheduler-3-expected.txt b/LayoutTests/storage/indexeddb/modern/transaction-scheduler-3-expected.txt
new file mode 100644 (file)
index 0000000..31c7a95
--- /dev/null
@@ -0,0 +1,11 @@
+ALERT: Upgrade needed: Old version - 0 New version - 1
+ALERT: versionchange transaction completed
+ALERT: Success opening database connection - Starting readonly transaction
+ALERT: Creating write transaction
+ALERT: Read transaction complete - [object Event]
+ALERT: Write transaction put success
+ALERT: Write transaction complete - [object Event]
+ALERT: Done
+This test makes sure that a write transaction is blocked by a read transaction with overlapping scope.
+It also makes sure the write transaction starts after the read transaction completes.
+
diff --git a/LayoutTests/storage/indexeddb/modern/transaction-scheduler-3.html b/LayoutTests/storage/indexeddb/modern/transaction-scheduler-3.html
new file mode 100644 (file)
index 0000000..0a72835
--- /dev/null
@@ -0,0 +1,142 @@
+This test makes sure that a write transaction is blocked by a read transaction with overlapping scope.<br>
+It also makes sure the write transaction starts after the read transaction completes.<br>
+<script>
+
+if (window.testRunner) {
+    testRunner.waitUntilDone();
+    testRunner.dumpAsText();
+}
+
+function done()
+{
+    alert("Done");
+    if (window.testRunner)
+        testRunner.notifyDone();
+}
+
+var createRequest = window.indexedDB.open("TransactionScheduler3Database");
+
+createRequest.onupgradeneeded = function(event) {
+    alert("Upgrade needed: Old version - " + event.oldVersion + " New version - " + event.newVersion);
+
+    var versionTransaction = createRequest.transaction;
+    var database = event.target.result;
+    var objectStore = database.createObjectStore("OS");
+    var request = objectStore.put("bar", "foo");
+
+    request.onerror = function(event) {
+        alert("put FAILED - " + event);
+        done();
+    }
+
+    versionTransaction.onabort = function(event) {
+        alert("versionchange transaction aborted");
+        done();
+    }
+
+    versionTransaction.oncomplete = function(event) {
+        alert("versionchange transaction completed");
+        continueTest();
+        database.close();
+    }
+
+    versionTransaction.onerror = function(event) {
+        alert("versionchange transaction error'ed - " + event);
+        done();
+    }
+}
+
+var secondDatabaseConnection;
+function continueTest()
+{
+    var longRunningReadRequest = window.indexedDB.open("TransactionScheduler3Database", 1);
+    longRunningReadRequest.onsuccess = function(event) {
+        alert("Success opening database connection - Starting readonly transaction");
+        secondDatabaseConnection = event.target.result;
+        readTransactionLoop(secondDatabaseConnection.transaction("OS", "readonly"), true);
+    }
+    longRunningReadRequest.onerror = function(event) {
+        alert("Long running read request unexpected error - " + event);
+        done();
+    }
+    longRunningReadRequest.onblocked = function(event) {
+        alert("Long running read request unexpected blocked - " + event);
+        done();
+    }
+    longRunningReadRequest.onupgradeneeded = function(event) {
+        alert("Long running read request unexpected upgradeneeded - " + event);
+        done();
+    } 
+}
+
+var shouldEndReadTransaction = false;
+
+function readTransactionLoop(transaction, isFirstTime)
+{
+    var objectStore = transaction.objectStore("OS");
+    var request = objectStore.get("foo");
+    
+    request.onsuccess = function(event) {
+        if (!shouldEndReadTransaction)
+            readTransactionLoop(event.target.transaction, false);
+        
+        // Now that the read transaction is open, the write transaction can be started, but it will be blocked to start with.
+        if (isFirstTime)
+             startWriteTransaction()
+    }
+
+    request.onerror = function(event) {
+        alert("Unexpected request error - " + event);
+        done();
+    }
+
+    transaction.onerror = function(event) {
+        alert("Unexpected transaction error - " + event);
+        done();
+    }
+
+    transaction.onabort = function(event) {
+        alert("Unexpected transaction abort - " + event);
+        done();
+    }
+
+    transaction.oncomplete = function(event) {
+        alert("Read transaction complete - " + event);
+    }
+}
+
+function startWriteTransaction()
+{
+    alert("Creating write transaction");
+    var transaction = secondDatabaseConnection.transaction("OS", "readwrite");
+    var objectStore = transaction.objectStore("OS");
+    var request = objectStore.put("baz", "foo");
+
+    setTimeout("shouldEndReadTransaction = true;", 0);
+
+    request.onsuccess = function(event) {
+        alert("Write transaction put success");
+    }
+
+    request.onerror = function(event) {
+        alert("Write transaction put unexpected error - " + event);
+        done();
+    }
+
+    transaction.onerror = function(event) {
+        alert("Write transaction unexpected error - " + event);
+        done();
+    }
+
+    transaction.onabort = function(event) {
+        alert("Write transaction unexpected abort - " + event);
+        done();
+    }
+
+    transaction.oncomplete = function(event) {
+        alert("Write transaction complete - " + event);
+        done();
+    }
+}
+
+</script>
diff --git a/LayoutTests/storage/indexeddb/modern/transaction-scheduler-4-expected.txt b/LayoutTests/storage/indexeddb/modern/transaction-scheduler-4-expected.txt
new file mode 100644 (file)
index 0000000..f916fd4
--- /dev/null
@@ -0,0 +1,20 @@
+ALERT: Upgrade needed: Old version - 0 New version - 1
+ALERT: versionchange transaction completed
+ALERT: Success opening database connection - Starting readonly transaction
+ALERT: Other objectstore read transaction get success - [object Event]
+ALERT: Other objectstore read transaction complete - [object Event]
+ALERT: Read transaction complete - [object Event]
+ALERT: Write transaction first put success
+ALERT: Second read-only transaction get success - [object Event]
+ALERT: Write transaction complete - [object Event]
+ALERT: Second read-only transaction complete - [object Event]
+ALERT: Done
+This test opens a long running read-only transaction to an object store, and then a read-write transaction to the same object store.
+The read-write transaction should be blocked while the long running read-only transaction is in progress.
+It also queues up a second read-only transaction to that same object store behind the blocked read-write transaction.
+Even though that second read-only transaction could start while the first is in progress and the read-write transaction is blocked,
+it should *not* start because doing so would risk continuing to block the read-write transaction.
+It also queues up a read-only to a different object store behind those first three transactions.
+That last read-only transaction *should* start while the read-write transaction is still blocked,
+as doing so won't risk blocking the read-write transaction further.
+
diff --git a/LayoutTests/storage/indexeddb/modern/transaction-scheduler-4.html b/LayoutTests/storage/indexeddb/modern/transaction-scheduler-4.html
new file mode 100644 (file)
index 0000000..3c659eb
--- /dev/null
@@ -0,0 +1,233 @@
+This test opens a long running read-only transaction to an object store, and then a read-write transaction to the same object store.<br>
+The read-write transaction should be blocked while the long running read-only transaction is in progress.<br>
+It also queues up a second read-only transaction to that same object store behind the blocked read-write transaction.<br>
+Even though that second read-only transaction could start while the first is in progress and the read-write transaction is blocked,<br>
+it should *not* start because doing so would risk continuing to block the read-write transaction.<br>
+It also queues up a read-only to a different object store behind those first three transactions.<br>
+That last read-only transaction *should* start while the read-write transaction is still blocked,<br>
+as doing so won't risk blocking the read-write transaction further.<br>
+<script>
+
+if (window.testRunner) {
+    testRunner.waitUntilDone();
+    testRunner.dumpAsText();
+}
+
+function done()
+{
+    alert("Done");
+    if (window.testRunner)
+        testRunner.notifyDone();
+}
+
+var createRequest = window.indexedDB.open("TransactionScheduler4Database");
+
+createRequest.onupgradeneeded = function(event) {
+    alert("Upgrade needed: Old version - " + event.oldVersion + " New version - " + event.newVersion);
+
+    var versionTransaction = createRequest.transaction;
+    var database = event.target.result;
+    var objectStore = database.createObjectStore("OS");
+    var request = objectStore.put("bar", "foo");
+
+    request.onerror = function(event) {
+        alert("first put FAILED - " + event);
+        done();
+    }
+
+    objectStore = database.createObjectStore("OtherOS");
+    request = objectStore.put("bar", "foo");
+    
+    request.onerror = function(event) {
+        alert("second put FAILED - " + event);
+        done();
+    }
+    
+    versionTransaction.onabort = function(event) {
+        alert("versionchange transaction aborted");
+        done();
+    }
+
+    versionTransaction.oncomplete = function(event) {
+        alert("versionchange transaction completed");
+        continueTest();
+        database.close();
+    }
+
+    versionTransaction.onerror = function(event) {
+        alert("versionchange transaction error'ed - " + event);
+        done();
+    }
+}
+
+var secondDatabaseConnection;
+function continueTest()
+{
+    var longRunningReadRequest = window.indexedDB.open("TransactionScheduler4Database", 1);
+    longRunningReadRequest.onsuccess = function(event) {
+        alert("Success opening database connection - Starting readonly transaction");
+        secondDatabaseConnection = event.target.result;
+        
+        var transaction = secondDatabaseConnection.transaction("OS", "readonly");
+        
+        transaction.onerror = function(event) {
+            alert("Unexpected transaction error - " + event);
+            done();
+        }
+
+        transaction.onabort = function(event) {
+            alert("Unexpected transaction abort - " + event);
+            done();
+        }
+
+        transaction.oncomplete = function(event) {
+            alert("Read transaction complete - " + event);
+        }
+        
+        firstReadTransactionLoop(transaction, true);
+    }
+    longRunningReadRequest.onerror = function(event) {
+        alert("Long running read request unexpected error - " + event);
+        done();
+    }
+    longRunningReadRequest.onblocked = function(event) {
+        alert("Long running read request unexpected blocked - " + event);
+        done();
+    }
+    longRunningReadRequest.onupgradeneeded = function(event) {
+        alert("Long running read request unexpected upgradeneeded - " + event);
+        done();
+    } 
+}
+
+var shouldEndReadTransaction = false;
+
+function firstReadTransactionLoop(transaction, isFirstTime)
+{
+    var objectStore = transaction.objectStore("OS");
+    var request = objectStore.get("foo");
+    
+    request.onsuccess = function(event) {
+        if (shouldEndReadTransaction)
+            return;
+            
+        firstReadTransactionLoop(event.target.transaction, false);
+        
+        // Now that the read transaction is open, the other transactions can be queued up.
+        if (isFirstTime)
+             startOtherTransactions()
+    }
+
+    request.onerror = function(event) {
+        alert("Unexpected request error - " + event);
+        done();
+    }
+}
+
+var shouldEndReadTransaction = false;
+var loggedWritePutSuccessOnce = false;
+function writeTransactionLoop(transaction)
+{
+    var objectStore = transaction.objectStore("OS");
+    var request = objectStore.put("baz", "foo");
+
+    request.onsuccess = function(event) {
+        if (!loggedWritePutSuccessOnce) {
+            alert("Write transaction first put success");
+            loggedWritePutSuccessOnce = true;
+        }
+        if (shouldEndReadTransaction)
+            return;
+        
+        writeTransactionLoop(transaction);
+    }
+    
+    request.onerror = function(event) {
+        alert("Write transaction put unexpected error - " + event);
+        done();
+    }
+}
+
+function startOtherTransactions()
+{
+    var transaction = secondDatabaseConnection.transaction("OS", "readwrite");
+
+    transaction.onerror = function(event) {
+        alert("Write transaction unexpected error - " + event);
+        done();
+    }
+
+    transaction.onabort = function(event) {
+        alert("Write transaction unexpected abort - " + event);
+        done();
+    }
+
+    transaction.oncomplete = function(event) {
+        alert("Write transaction complete - " + event);
+    }
+    
+    writeTransactionLoop(transaction);
+
+    // This read-only transaction should be blocked behind the read-write transaction above, 
+    // and should not start until it finishes.
+    transaction = secondDatabaseConnection.transaction("OS", "readonly");
+    transaction.onerror = function(event) {
+        alert("Second read-only transaction unexpected error - " + event);
+        done();
+    }
+
+    transaction.onabort = function(event) {
+        alert("Second read-only transaction unexpected abort - " + event);
+        done();
+    }
+
+    transaction.oncomplete = function(event) {
+        alert("Second read-only transaction complete - " + event);
+        done();
+    }
+    
+    var objectStore = transaction.objectStore("OS");
+    var request = objectStore.get("foo");
+
+    request.onsuccess = function(event) {
+        alert("Second read-only transaction get success - " + event);
+    }
+    
+    request.onerror = function(event) {
+        alert("Second read-only transaction put unexpected error - " + event);
+        done();
+    }
+
+    // This read-only transaction is queued behind the 3 previous transactions, but should not
+    // be blocked because its scope doesn't overlap the read-write transaction.
+    transaction = secondDatabaseConnection.transaction("OtherOS", "readonly");
+
+    transaction.onerror = function(event) {
+        alert("Other objectstore read transaction unexpected error - " + event);
+        done();
+    }
+
+    transaction.onabort = function(event) {
+        alert("Other objectstore read transaction unexpected abort - " + event);
+        done();
+    }
+
+    transaction.oncomplete = function(event) {
+        alert("Other objectstore read transaction complete - " + event);
+        shouldEndReadTransaction = true;
+    }
+
+    objectStore = transaction.objectStore("OtherOS");
+    request = objectStore.get("foo");
+
+    request.onsuccess = function(event) {
+        alert("Other objectstore read transaction get success - " + event);
+    }
+    
+    request.onerror = function(event) {
+        alert("Other objectstore read transaction get unexpected error - " + event);
+        done();
+    }
+}
+
+</script>
diff --git a/LayoutTests/storage/indexeddb/modern/transaction-scheduler-5-expected.txt b/LayoutTests/storage/indexeddb/modern/transaction-scheduler-5-expected.txt
new file mode 100644 (file)
index 0000000..c7481d6
--- /dev/null
@@ -0,0 +1,14 @@
+ALERT: Upgrade needed: Old version - 0 New version - 1
+ALERT: versionchange transaction completed
+ALERT: Success opening database connection - Starting transactions
+ALERT: Starting write transaction
+ALERT: Read transaction complete - [object Event]
+ALERT: Read transaction complete - [object Event]
+ALERT: Write to OS1 successful
+ALERT: Write to OS2 successful
+ALERT: Done
+This test creates two object stores.
+It then opens two long running read-only transactions, one per object store.
+It then opens a read-write transaction to both object stores.
+The read-only transactions both need to finish before the read-write transaction starts.
+
diff --git a/LayoutTests/storage/indexeddb/modern/transaction-scheduler-5.html b/LayoutTests/storage/indexeddb/modern/transaction-scheduler-5.html
new file mode 100644 (file)
index 0000000..b4ea6bb
--- /dev/null
@@ -0,0 +1,166 @@
+This test creates two object stores.<br>
+It then opens two long running read-only transactions, one per object store.<br>
+It then opens a read-write transaction to both object stores.<br>
+The read-only transactions both need to finish before the read-write transaction starts.<br>
+<script>
+
+if (window.testRunner) {
+    testRunner.waitUntilDone();
+    testRunner.dumpAsText();
+}
+
+function done()
+{
+    alert("Done");
+    if (window.testRunner)
+        testRunner.notifyDone();
+}
+
+var createRequest = window.indexedDB.open("TransactionScheduler5Database");
+
+createRequest.onupgradeneeded = function(event) {
+    alert("Upgrade needed: Old version - " + event.oldVersion + " New version - " + event.newVersion);
+
+    var versionTransaction = createRequest.transaction;
+    var database = event.target.result;
+    var objectStore = database.createObjectStore("OS1");
+    var request = objectStore.put("bar", "foo");
+
+    request.onerror = function(event) {
+        alert("first put FAILED - " + event);
+        done();
+    }
+
+    objectStore = database.createObjectStore("OS2");
+    request = objectStore.put("bar", "foo");
+    
+    request.onerror = function(event) {
+        alert("second put FAILED - " + event);
+        done();
+    }
+    
+    versionTransaction.onabort = function(event) {
+        alert("versionchange transaction aborted");
+        done();
+    }
+
+    versionTransaction.oncomplete = function(event) {
+        alert("versionchange transaction completed");
+        continueTest();
+        database.close();
+    }
+
+    versionTransaction.onerror = function(event) {
+        alert("versionchange transaction error'ed - " + event);
+        done();
+    }
+}
+
+var secondDatabaseConnection;
+var readWriteTransaction;
+
+function setupReadTransactionCallbacks(transaction)
+{
+    transaction.onerror = function(event) {
+        alert("Unexpected transaction error - " + event);
+        done();
+    }
+
+    transaction.onabort = function(event) {
+        alert("Unexpected transaction abort - " + event);
+        done();
+    }
+
+    transaction.oncomplete = function(event) {
+        alert("Read transaction complete - " + event);
+    }
+
+    transaction.hasDoneFirstRead = false;
+}
+
+function continueTest()
+{
+    var openDBRequest = window.indexedDB.open("TransactionScheduler5Database", 1);
+    openDBRequest.onsuccess = function(event) {
+        alert("Success opening database connection - Starting transactions");
+        secondDatabaseConnection = event.target.result;
+        
+        var transaction1 = secondDatabaseConnection.transaction("OS1", "readonly");
+        setupReadTransactionCallbacks(transaction1);
+        var transaction2 = secondDatabaseConnection.transaction("OS2", "readonly");
+        setupReadTransactionCallbacks(transaction2);
+
+        readTransactionLoop(transaction1, "OS1", false);
+        readTransactionLoop(transaction2, "OS2", true);
+    }
+    openDBRequest.onerror = function(event) {
+        alert("Long running read request unexpected error - " + event);
+        done();
+    }
+    openDBRequest.onblocked = function(event) {
+        alert("Long running read request unexpected blocked - " + event);
+        done();
+    }
+    openDBRequest.onupgradeneeded = function(event) {
+        alert("Long running read request unexpected upgradeneeded - " + event);
+        done();
+    } 
+}
+
+var shouldEndReadTransaction = false;
+
+function readTransactionLoop(transaction, osname, shouldStartWrite)
+{
+    var objectStore = transaction.objectStore(osname);
+    var request = objectStore.get("foo");
+    
+    request.onsuccess = function(event) {
+        if (shouldEndReadTransaction && transaction.hasDoneFirstRead)
+            return;
+
+        transaction.hasDoneFirstRead = true;
+        readTransactionLoop(event.target.transaction, osname, false);
+        
+        if (shouldStartWrite)
+             startWriteTransaction();
+    }
+
+    request.onerror = function(event) {
+        alert("Unexpected request error - " + event);
+        done();
+    }
+}
+
+function startWriteTransaction()
+{
+    alert("Starting write transaction");
+    var transaction = secondDatabaseConnection.transaction(["OS1", "OS2"], "readwrite");
+    var objectStore = transaction.objectStore("OS1");
+    var request = objectStore.put("baz", "foo");
+
+    shouldEndReadTransaction = true;
+
+    request.onsuccess = function(event) {
+        alert("Write to OS1 successful");
+    }
+    
+    request.onerror = function(event) {
+        alert("Write transaction put unexpected error - " + event);
+        done();
+    }
+
+    objectStore = transaction.objectStore("OS2");
+    request = objectStore.put("baz", "foo");
+
+    request.onsuccess = function(event) {
+        alert("Write to OS2 successful");
+        done();
+    }
+    
+    request.onerror = function(event) {
+        alert("Write transaction put unexpected error - " + event);
+        done();
+    }
+}
+
+</script>
diff --git a/LayoutTests/storage/indexeddb/modern/transaction-scheduler-6-expected.txt b/LayoutTests/storage/indexeddb/modern/transaction-scheduler-6-expected.txt
new file mode 100644 (file)
index 0000000..e38df0b
--- /dev/null
@@ -0,0 +1,12 @@
+ALERT: Upgrade needed: Old version - 0 New version - 1
+ALERT: versionchange transaction completed
+ALERT: Starting a readonly transaction
+ALERT: Starting a readonly transaction
+ALERT: readonly transaction completed
+ALERT: readonly transaction completed
+ALERT: Write in readwrite transaction succeeded
+ALERT: readwrite transaction completed
+ALERT: Done
+This test starts two read-only transactions to an object store followed by a read-write transaction.
+It verifies that the read-write doesn't start until both read-onlys have finished.
+
diff --git a/LayoutTests/storage/indexeddb/modern/transaction-scheduler-6.html b/LayoutTests/storage/indexeddb/modern/transaction-scheduler-6.html
new file mode 100644 (file)
index 0000000..0167717
--- /dev/null
@@ -0,0 +1,124 @@
+This test starts two read-only transactions to an object store followed by a read-write transaction.<br>
+It verifies that the read-write doesn't start until both read-onlys have finished.<br>
+<script>
+
+if (window.testRunner) {
+    testRunner.waitUntilDone();
+    testRunner.dumpAsText();
+}
+
+function done()
+{
+    alert("Done");
+    if (window.testRunner)
+        testRunner.notifyDone();
+}
+
+var createRequest = window.indexedDB.open("TransactionScheduler6Database");
+var database;
+
+createRequest.onupgradeneeded = function(event) {
+    alert("Upgrade needed: Old version - " + event.oldVersion + " New version - " + event.newVersion);
+
+    var versionTransaction = createRequest.transaction;
+    database = event.target.result;
+    var objectStore = database.createObjectStore("TestObjectStore");
+    var request = objectStore.put("foo", "bar");
+
+    request.onerror = function(event) {
+        alert("put FAILED - " + event);
+        done();
+    }
+    
+    versionTransaction.onabort = function(event) {
+        alert("versionchange transaction aborted");
+        done();
+    }
+
+    versionTransaction.oncomplete = function(event) {
+        alert("versionchange transaction completed");
+        continueTest();
+    }
+
+    versionTransaction.onerror = function(event) {
+        alert("versionchange transaction error'ed - " + event);
+        done();
+    }
+}
+
+function continueTest()
+{
+    startTransactionLoop(database.transaction("TestObjectStore", "readonly"), true);
+    startTransactionLoop(database.transaction("TestObjectStore", "readonly"), true);
+    
+    var transaction = database.transaction("TestObjectStore", "readwrite");
+    var objectStore = transaction.objectStore("TestObjectStore");
+    var request = objectStore.put("baz", "foo");
+
+    request.onsuccess = function(event) {
+        alert("Write in readwrite transaction succeeded");
+    }
+    
+    request.onerror = function(event) {
+        alert("Write in readwrite transaction unexpectedly failed");
+        done();
+    }
+    
+    transaction.onabort = function(event) {
+        alert("readwrite transaction expectedly aborted");
+        done();
+    }
+
+    transaction.oncomplete = function(event) {
+        alert("readwrite transaction completed");
+        done();
+    }
+
+    transaction.onerror = function(event) {
+        alert("readwrite transaction error'ed - " + event);
+        done();
+    }
+}
+
+var numberOfOpenTransactions = 0;
+
+function startTransactionLoop(transaction, isFirstTime)
+{
+    var objectStore = transaction.objectStore("TestObjectStore");
+    var request = objectStore.get("bar");
+    
+    request.onsuccess = function(event) {
+        if (isFirstTime) {
+            alert("Starting a readonly transaction");
+            numberOfOpenTransactions++;
+        }
+        
+        if (numberOfOpenTransactions == 2)
+            return;
+
+        startTransactionLoop(event.target.transaction, false);
+    }
+
+    request.onerror = function(event) {
+        alert("Unexpected request error - " + event);
+        done();
+    }
+
+    transaction.onerror = function(event) {
+        alert("Unexpected transaction error - " + event);
+        done();
+    }
+
+    transaction.onabort = function(event) {
+        --numberOfOpenTransactions;
+        alert("Unexpected transaction abort - " + event);
+        done();
+    }
+
+    transaction.oncomplete = function(event) {
+        --numberOfOpenTransactions;
+        alert("readonly transaction completed");
+    }
+}
+
+</script>
index b6fd7dc..a8aae55 100644 (file)
@@ -1,3 +1,109 @@
+2015-10-28  Brady Eidson  <beidson@apple.com>
+
+        Modern IDB: Support IDBDatabase.transaction() (and transaction scheduling in general).
+        https://bugs.webkit.org/show_bug.cgi?id=150614
+
+        Reviewed by Alex Christensen.
+
+        Tests: storage/indexeddb/modern/idbdatabase-transaction-failures.html
+               storage/indexeddb/modern/transaction-scheduler-1.html
+               storage/indexeddb/modern/transaction-scheduler-2.html
+               storage/indexeddb/modern/transaction-scheduler-3.html
+               storage/indexeddb/modern/transaction-scheduler-4.html
+               storage/indexeddb/modern/transaction-scheduler-5.html
+               storage/indexeddb/modern/transaction-scheduler-6.html
+
+        * Modules/indexeddb/IDBDatabase.idl:
+
+        * Modules/indexeddb/IndexedDB.h:
+
+        * Modules/indexeddb/client/IDBConnectionToServer.cpp:
+        (WebCore::IDBClient::IDBConnectionToServer::establishTransaction):
+        (WebCore::IDBClient::IDBConnectionToServer::didStartTransaction):
+        (WebCore::IDBClient::IDBConnectionToServer::hasRecordOfTransaction):
+        * Modules/indexeddb/client/IDBConnectionToServer.h:
+        * Modules/indexeddb/client/IDBConnectionToServerDelegate.h:
+
+        * Modules/indexeddb/client/IDBDatabaseImpl.cpp:
+        (WebCore::IDBClient::IDBDatabase::transaction):
+        (WebCore::IDBClient::IDBDatabase::didStartTransaction):
+        * Modules/indexeddb/client/IDBDatabaseImpl.h:
+
+        * Modules/indexeddb/client/IDBTransactionImpl.cpp:
+        (WebCore::IDBClient::IDBTransaction::IDBTransaction):
+        (WebCore::IDBClient::IDBTransaction::operationTimerFired):
+        (WebCore::IDBClient::IDBTransaction::didStart):
+        (WebCore::IDBClient::IDBTransaction::establishOnServer):
+        (WebCore::IDBClient::IDBTransaction::activate):
+        (WebCore::IDBClient::IDBTransaction::deactivate):
+        * Modules/indexeddb/client/IDBTransactionImpl.h:
+
+        * Modules/indexeddb/server/IDBConnectionToClient.cpp:
+        (WebCore::IDBServer::IDBConnectionToClient::didStartTransaction):
+        * Modules/indexeddb/server/IDBConnectionToClient.h:
+        * Modules/indexeddb/server/IDBConnectionToClientDelegate.h:
+
+        * Modules/indexeddb/server/IDBServer.cpp:
+        (WebCore::IDBServer::IDBServer::establishTransaction):
+        * Modules/indexeddb/server/IDBServer.h:
+
+        * Modules/indexeddb/server/MemoryIDBBackingStore.cpp:
+        (WebCore::IDBServer::MemoryIDBBackingStore::beginTransaction):
+        (WebCore::IDBServer::MemoryIDBBackingStore::createObjectStore):
+        (WebCore::IDBServer::MemoryIDBBackingStore::removeObjectStoreForVersionChangeAbort):
+        (WebCore::IDBServer::MemoryIDBBackingStore::keyExistsInObjectStore):
+        (WebCore::IDBServer::MemoryIDBBackingStore::deleteRecord):
+        (WebCore::IDBServer::MemoryIDBBackingStore::putRecord):
+        (WebCore::IDBServer::MemoryIDBBackingStore::getRecord):
+        (WebCore::IDBServer::MemoryIDBBackingStore::registerObjectStore):
+        (WebCore::IDBServer::MemoryIDBBackingStore::unregisterObjectStore):
+        * Modules/indexeddb/server/MemoryIDBBackingStore.h:
+
+        * Modules/indexeddb/server/UniqueIDBDatabase.cpp:
+        (WebCore::IDBServer::UniqueIDBDatabase::startVersionChangeTransaction):
+        (WebCore::IDBServer::UniqueIDBDatabase::performCommitTransaction):
+        (WebCore::IDBServer::UniqueIDBDatabase::didPerformCommitTransaction):
+        (WebCore::IDBServer::UniqueIDBDatabase::didPerformAbortTransaction):
+        (WebCore::IDBServer::UniqueIDBDatabase::enqueueTransaction):
+        (WebCore::IDBServer::UniqueIDBDatabase::transactionSchedulingTimerFired):
+        (WebCore::IDBServer::UniqueIDBDatabase::activateTransactionInBackingStore):
+        (WebCore::IDBServer::UniqueIDBDatabase::performActivateTransactionInBackingStore):
+        (WebCore::IDBServer::UniqueIDBDatabase::didPerformActivateTransactionInBackingStore):
+        (WebCore::IDBServer::scopesOverlap):
+        (WebCore::IDBServer::UniqueIDBDatabase::takeNextRunnableTransaction):
+        (WebCore::IDBServer::UniqueIDBDatabase::inProgressTransactionCompleted):
+        * Modules/indexeddb/server/UniqueIDBDatabase.h:
+
+        * Modules/indexeddb/server/UniqueIDBDatabaseConnection.cpp:
+        (WebCore::IDBServer::UniqueIDBDatabaseConnection::establishTransaction):
+        * Modules/indexeddb/server/UniqueIDBDatabaseConnection.h:
+
+        * Modules/indexeddb/server/UniqueIDBDatabaseTransaction.cpp:
+        (WebCore::IDBServer::UniqueIDBDatabaseTransaction::create):
+        (WebCore::IDBServer::UniqueIDBDatabaseTransaction::UniqueIDBDatabaseTransaction):
+        (WebCore::IDBServer::UniqueIDBDatabaseTransaction::objectStoreIdentifiers):
+        (WebCore::IDBServer::UniqueIDBDatabaseTransaction::didActivateInBackingStore):
+        * Modules/indexeddb/server/UniqueIDBDatabaseTransaction.h:
+        
+        * Modules/indexeddb/shared/IDBTransactionInfo.cpp:
+        (WebCore::IDBTransactionInfo::clientTransaction):
+        (WebCore::IDBTransactionInfo::isolatedCopy):
+        * Modules/indexeddb/shared/IDBTransactionInfo.h:
+        
+        * Modules/indexeddb/shared/InProcessIDBServer.cpp:
+        (WebCore::InProcessIDBServer::establishTransaction):
+        (WebCore::InProcessIDBServer::didStartTransaction):
+        * Modules/indexeddb/shared/InProcessIDBServer.h:
+        
+        * bindings/js/IDBBindingUtilities.cpp:
+        (WebCore::deserializeIDBValueData):
+        
+        * bindings/js/JSIDBDatabaseCustom.cpp:
+        (WebCore::JSIDBDatabase::transaction):
+        
+        * bindings/js/ScriptState.cpp:
+        (WebCore::execStateFromPage):
+
 2015-10-28  Eric Carlson  <eric.carlson@apple.com>
 
         [MediaStream] Play MediaStream through media element and rendered to canvas
index a5def70..fc7dea6 100644 (file)
 
     [Custom, RaisesException] IDBObjectStore createObjectStore(DOMString name, optional Dictionary options);
     [RaisesException] void deleteObjectStore(DOMString name);
-    [CallWith=ScriptExecutionContext, RaisesException] IDBTransaction transaction(DOMString storeName, [Default=NullString] optional DOMString mode);
-    [CallWith=ScriptExecutionContext, RaisesException] IDBTransaction transaction(sequence<DOMString> storeNames, [Default=NullString] optional DOMString mode);
+    [Custom, CallWith=ScriptExecutionContext, RaisesException] IDBTransaction transaction(DOMString storeName, [Default=NullString] optional DOMString mode);
+    [Custom, CallWith=ScriptExecutionContext, RaisesException] IDBTransaction transaction(sequence<DOMString> storeNames, [Default=NullString] optional DOMString mode);
     void close();
 
     attribute EventHandler onabort;
     attribute EventHandler onerror;
     attribute EventHandler onversionchange;
-
 };
 
index 8fef522..46a4a3d 100644 (file)
@@ -40,7 +40,6 @@ enum class TransactionMode {
 const unsigned TransactionModeMaximum = 2;
 
 enum class TransactionState {
-    Unstarted,
     Active,
     Inactive,
     Committing,
index e610325..f771e97 100644 (file)
@@ -139,6 +139,16 @@ void IDBConnectionToServer::didGetRecord(const IDBResultData& resultData)
     completeOperation(resultData);
 }
 
+void IDBConnectionToServer::establishTransaction(IDBTransaction& transaction)
+{
+    LOG(IndexedDB, "IDBConnectionToServer::establishTransaction");
+
+    ASSERT(!hasRecordOfTransaction(transaction));
+    m_pendingTransactions.set(transaction.info().identifier(), &transaction);
+
+    m_delegate->establishTransaction(transaction.database().databaseConnectionIdentifier(), transaction.info());
+}
+
 void IDBConnectionToServer::commitTransaction(IDBTransaction& transaction)
 {
     LOG(IndexedDB, "IDBConnectionToServer::commitTransaction");
@@ -190,6 +200,16 @@ void IDBConnectionToServer::fireVersionChangeEvent(uint64_t databaseConnectionId
     connection->fireVersionChangeEvent(requestedVersion);
 }
 
+void IDBConnectionToServer::didStartTransaction(const IDBResourceIdentifier& transactionIdentifier, const IDBError& error)
+{
+    LOG(IndexedDB, "IDBConnectionToServer::didStartTransaction");
+
+    auto transaction = m_pendingTransactions.take(transactionIdentifier);
+    ASSERT(transaction);
+
+    transaction->didStart(error);
+}
+
 void IDBConnectionToServer::databaseConnectionClosed(IDBDatabase& database)
 {
     LOG(IndexedDB, "IDBConnectionToServer::databaseConnectionClosed");
@@ -224,6 +244,12 @@ void IDBConnectionToServer::completeOperation(const IDBResultData& resultData)
     operation->completed(resultData);
 }
 
+bool IDBConnectionToServer::hasRecordOfTransaction(const IDBTransaction& transaction) const
+{
+    auto identifier = transaction.info().identifier();
+    return m_pendingTransactions.contains(identifier) || m_committingTransactions.contains(identifier) || m_abortingTransactions.contains(identifier);
+}
+
 } // namespace IDBClient
 } // namespace WebCore
 
index 7e27848..ee454e7 100644 (file)
@@ -77,6 +77,9 @@ public:
     void didAbortTransaction(const IDBResourceIdentifier& transactionIdentifier, const IDBError&);
 
     void fireVersionChangeEvent(uint64_t databaseConnectionIdentifier, uint64_t requestedVersion);
+    void didStartTransaction(const IDBResourceIdentifier& transactionIdentifier, const IDBError&);
+
+    void establishTransaction(IDBTransaction&);
 
     void databaseConnectionClosed(IDBDatabase&);
     void registerDatabaseConnection(IDBDatabase&);
@@ -88,10 +91,13 @@ private:
     void saveOperation(TransactionOperation&);
     void completeOperation(const IDBResultData&);
 
+    bool hasRecordOfTransaction(const IDBTransaction&) const;
+
     Ref<IDBConnectionToServerDelegate> m_delegate;
 
     HashMap<IDBResourceIdentifier, RefPtr<IDBClient::IDBOpenDBRequest>> m_openDBRequestMap;
     HashMap<uint64_t, IDBDatabase*> m_databaseConnectionMap;
+    HashMap<IDBResourceIdentifier, RefPtr<IDBTransaction>> m_pendingTransactions;
     HashMap<IDBResourceIdentifier, RefPtr<IDBTransaction>> m_committingTransactions;
     HashMap<IDBResourceIdentifier, RefPtr<IDBTransaction>> m_abortingTransactions;
     HashMap<IDBResourceIdentifier, RefPtr<TransactionOperation>> m_activeOperations;
index ef268cb..9ea5ca6 100644 (file)
@@ -34,6 +34,7 @@ class IDBKey;
 class IDBObjectStoreInfo;
 class IDBRequestData;
 class IDBResourceIdentifier;
+class IDBTransactionInfo;
 class SerializedScriptValue;
 
 namespace IndexedDB {
@@ -54,6 +55,7 @@ public:
     virtual void createObjectStore(const IDBRequestData&, const IDBObjectStoreInfo&) = 0;
     virtual void putOrAdd(const IDBRequestData&, IDBKey*, SerializedScriptValue&, const IndexedDB::ObjectStoreOverwriteMode) = 0;
     virtual void getRecord(const IDBRequestData&, IDBKey*) = 0;
+    virtual void establishTransaction(uint64_t databaseConnectionIdentifier, const IDBTransactionInfo&) = 0;
 
     virtual void databaseConnectionClosed(uint64_t databaseConnectionIdentifier) = 0;
 
index c038909..1154a91 100644 (file)
@@ -128,16 +128,54 @@ RefPtr<WebCore::IDBObjectStore> IDBDatabase::createObjectStore(const String& nam
     return adoptRef(&objectStore.leakRef());
 }
 
-RefPtr<WebCore::IDBTransaction> IDBDatabase::transaction(ScriptExecutionContext*, const Vector<String>&, const String&, ExceptionCode&)
+RefPtr<WebCore::IDBTransaction> IDBDatabase::transaction(ScriptExecutionContext*, const Vector<String>& objectStores, const String& modeString, ExceptionCode& ec)
 {
-    ASSERT_NOT_REACHED();
-    return nullptr;
+    LOG(IndexedDB, "IDBDatabase::transaction");
+
+    if (m_closePending) {
+        ec = INVALID_STATE_ERR;
+        return nullptr;
+    }
+
+    if (objectStores.isEmpty()) {
+        ec = INVALID_ACCESS_ERR;
+        return nullptr;
+    }
+
+    IndexedDB::TransactionMode mode = IDBTransaction::stringToMode(modeString, ec);
+    if (ec)
+        return nullptr;
+
+    if (mode != IndexedDB::TransactionMode::ReadOnly && mode != IndexedDB::TransactionMode::ReadWrite) {
+        ec = TypeError;
+        return nullptr;
+    }
+
+    if (m_versionChangeTransaction) {
+        ec = INVALID_STATE_ERR;
+        return nullptr;
+    }
+
+    for (auto& objectStoreName : objectStores) {
+        if (m_info.hasObjectStore(objectStoreName))
+            continue;
+        ec = NOT_FOUND_ERR;
+        return nullptr;
+    }
+
+    auto info = IDBTransactionInfo::clientTransaction(m_serverConnection.get(), objectStores, mode);
+    auto transaction = IDBTransaction::create(*this, info);
+
+    m_activeTransactions.set(info.identifier(), &transaction.get());
+
+    return adoptRef(&transaction.leakRef());
 }
 
-RefPtr<WebCore::IDBTransaction> IDBDatabase::transaction(ScriptExecutionContext*, const String&, const String&, ExceptionCode&)
+RefPtr<WebCore::IDBTransaction> IDBDatabase::transaction(ScriptExecutionContext* context, const String& objectStore, const String& mode, ExceptionCode& ec)
 {
-    ASSERT_NOT_REACHED();
-    return nullptr;
+    Vector<String> objectStores(1);
+    objectStores[0] = objectStore;
+    return transaction(context, objectStores, mode, ec);
 }
 
 void IDBDatabase::deleteObjectStore(const String&, ExceptionCode&)
@@ -193,6 +231,14 @@ Ref<IDBTransaction> IDBDatabase::startVersionChangeTransaction(const IDBTransact
     return WTF::move(transaction);
 }
 
+void IDBDatabase::didStartTransaction(IDBTransaction& transaction)
+{
+    LOG(IndexedDB, "IDBDatabase::didStartTransaction");
+    ASSERT(!m_versionChangeTransaction);
+
+    m_activeTransactions.set(transaction.info().identifier(), &transaction);
+}
+
 void IDBDatabase::commitTransaction(IDBTransaction& transaction)
 {
     LOG(IndexedDB, "IDBDatabase::commitTransaction");
index d6c7187..1f86256 100644 (file)
@@ -73,6 +73,8 @@ public:
     uint64_t databaseConnectionIdentifier() const { return m_databaseConnectionIdentifier; }
 
     Ref<IDBTransaction> startVersionChangeTransaction(const IDBTransactionInfo&);
+    void didStartTransaction(IDBTransaction&);
+
     void commitTransaction(IDBTransaction&);
     void didCommitTransaction(IDBTransaction&);
     void abortTransaction(IDBTransaction&);
index 2ec2ffc..d80527a 100644 (file)
@@ -55,14 +55,18 @@ IDBTransaction::IDBTransaction(IDBDatabase& database, const IDBTransactionInfo&
     , m_operationTimer(*this, &IDBTransaction::operationTimerFired)
 
 {
-    m_activationTimer = std::make_unique<Timer>(*this, &IDBTransaction::activationTimerFired);
-    m_activationTimer->startOneShot(0);
+    relaxAdoptionRequirement();
 
-    if (m_info.mode() == IndexedDB::TransactionMode::VersionChange)
+    if (m_info.mode() == IndexedDB::TransactionMode::VersionChange) {
+        m_activationTimer = std::make_unique<Timer>(*this, &IDBTransaction::activationTimerFired);
+        m_activationTimer->startOneShot(0);
         m_originalDatabaseInfo = std::make_unique<IDBDatabaseInfo>(m_database->info());
+        m_state = IndexedDB::TransactionState::Inactive;
+        m_startedOnServer = true;
+    } else
+        establishOnServer();
 
     suspendIfNeeded();
-    m_state = IndexedDB::TransactionState::Inactive;
 }
 
 IDBTransaction::~IDBTransaction()
@@ -213,7 +217,7 @@ void IDBTransaction::operationTimerFired()
 {
     LOG(IndexedDB, "IDBTransaction::operationTimerFired");
 
-    if (m_state == IndexedDB::TransactionState::Unstarted)
+    if (!m_startedOnServer)
         return;
 
     if (!m_transactionOperationQueue.isEmpty()) {
@@ -247,6 +251,24 @@ void IDBTransaction::finishAbortOrCommit()
     m_originalDatabaseInfo = nullptr;
 }
 
+void IDBTransaction::didStart(const IDBError& error)
+{
+    LOG(IndexedDB, "IDBTransaction::didStart");
+
+    m_database->didStartTransaction(*this);
+
+    m_startedOnServer = true;
+
+    // It's possible the transaction failed to start on the server.
+    // That equates to an abort.
+    if (!error.isNull()) {
+        didAbort(error);
+        return;
+    }
+
+    scheduleOperationTimer();
+}
+
 void IDBTransaction::didAbort(const IDBError& error)
 {
     LOG(IndexedDB, "IDBTransaction::didAbort");
@@ -416,15 +438,22 @@ void IDBTransaction::didPutOrAddOnServer(IDBRequest& request, const IDBResultDat
     request.requestCompleted(resultData);
 }
 
+void IDBTransaction::establishOnServer()
+{
+    LOG(IndexedDB, "IDBTransaction::establishOnServer");
+
+    serverConnection().establishTransaction(*this);
+}
+
 void IDBTransaction::activate()
 {
-    ASSERT(m_state == IndexedDB::TransactionState::Unstarted || m_state == IndexedDB::TransactionState::Inactive);
+    ASSERT(!isFinishedOrFinishing());
     m_state = IndexedDB::TransactionState::Active;
 }
 
 void IDBTransaction::deactivate()
 {
-    if (m_state == IndexedDB::TransactionState::Unstarted || m_state == IndexedDB::TransactionState::Active)
+    if (m_state == IndexedDB::TransactionState::Active)
         m_state = IndexedDB::TransactionState::Inactive;
 
     scheduleOperationTimer();
index 3e7e765..97d039a 100644 (file)
@@ -77,6 +77,7 @@ public:
     const IDBDatabase& database() const { return m_database.get(); }
     IDBDatabaseInfo* originalDatabaseInfo() const { return m_originalDatabaseInfo.get(); }
 
+    void didStart(const IDBError&);
     void didAbort(const IDBError&);
     void didCommit(const IDBError&);
 
@@ -121,11 +122,15 @@ private:
     void getRecordOnServer(TransactionOperation&, RefPtr<IDBKey>);
     void didGetRecordOnServer(IDBRequest&, const IDBResultData&);
 
+    void establishOnServer();
+
     Ref<IDBDatabase> m_database;
     IDBTransactionInfo m_info;
     std::unique_ptr<IDBDatabaseInfo> m_originalDatabaseInfo;
 
-    IndexedDB::TransactionState m_state { IndexedDB::TransactionState::Unstarted };
+    IndexedDB::TransactionState m_state { IndexedDB::TransactionState::Active };
+    bool m_startedOnServer { false };
+
     IDBError m_idbError;
 
     Timer m_operationTimer;
index 431a4fd..4546928 100644 (file)
@@ -88,6 +88,11 @@ void IDBConnectionToClient::fireVersionChangeEvent(UniqueIDBDatabaseConnection&
     m_delegate->fireVersionChangeEvent(connection, requestedVersion);
 }
 
+void IDBConnectionToClient::didStartTransaction(const IDBResourceIdentifier& transactionIdentifier, const IDBError& error)
+{
+    m_delegate->didStartTransaction(transactionIdentifier, error);
+}
+
 } // namespace IDBServer
 } // namespace WebCore
 
index bff856e..72dd2b2 100644 (file)
@@ -57,6 +57,7 @@ public:
     void didGetRecord(const IDBResultData&);
 
     void fireVersionChangeEvent(UniqueIDBDatabaseConnection&, uint64_t requestedVersion);
+    void didStartTransaction(const IDBResourceIdentifier& transactionIdentifier, const IDBError&);
 
 private:
     IDBConnectionToClient(IDBConnectionToClientDelegate&);
index 1027f4f..22446a7 100644 (file)
@@ -53,6 +53,7 @@ public:
     virtual void didGetRecord(const IDBResultData&) = 0;
 
     virtual void fireVersionChangeEvent(UniqueIDBDatabaseConnection&, uint64_t requestedVersion) = 0;
+    virtual void didStartTransaction(const IDBResourceIdentifier& transactionIdentifier, const IDBError&) = 0;
 
     virtual void ref() = 0;
     virtual void deref() = 0;
index 1c40184..33ea49c 100644 (file)
@@ -190,6 +190,17 @@ void IDBServer::getRecord(const IDBRequestData& requestData, const IDBKeyData& k
     transaction->getRecord(requestData, keyData);
 }
 
+void IDBServer::establishTransaction(uint64_t databaseConnectionIdentifier, const IDBTransactionInfo& info)
+{
+    LOG(IndexedDB, "IDBServer::establishTransaction");
+
+    auto databaseConnection = m_databaseConnections.get(databaseConnectionIdentifier);
+    if (!databaseConnection)
+        return;
+
+    databaseConnection->establishTransaction(info);
+}
+
 void IDBServer::commitTransaction(const IDBResourceIdentifier& transactionIdentifier)
 {
     LOG(IndexedDB, "IDBServer::commitTransaction");
index ff2696c..93b8c0c 100644 (file)
@@ -62,6 +62,7 @@ public:
     void createObjectStore(const IDBRequestData&, const IDBObjectStoreInfo&);
     void putOrAdd(const IDBRequestData&, const IDBKeyData&, const ThreadSafeDataBuffer& valueData, IndexedDB::ObjectStoreOverwriteMode);
     void getRecord(const IDBRequestData&, const IDBKeyData&);
+    void establishTransaction(uint64_t databaseConnectionIdentifier, const IDBTransactionInfo&);
     void databaseConnectionClosed(uint64_t databaseConnectionIdentifier);
 
     void postDatabaseTask(std::unique_ptr<CrossThreadTask>&&);
index a818d19..09b4536 100644 (file)
@@ -75,8 +75,13 @@ IDBError MemoryIDBBackingStore::beginTransaction(const IDBTransactionInfo& info)
 
     // VersionChange transactions are scoped to "every object store".
     if (transaction->isVersionChange()) {
-        for (auto& objectStore : m_objectStores.values())
+        for (auto& objectStore : m_objectStoresByIdentifier.values())
             transaction->addExistingObjectStore(*objectStore);
+    } else if (transaction->isWriting()) {
+        for (auto& iterator : m_objectStoresByName) {
+            if (info.objectStores().contains(iterator.key))
+                transaction->addExistingObjectStore(*iterator.value);
+        }
     }
 
     m_transactions.set(info.identifier(), WTF::move(transaction));
@@ -118,7 +123,7 @@ IDBError MemoryIDBBackingStore::createObjectStore(const IDBResourceIdentifier& t
     if (m_databaseInfo->hasObjectStore(info.name()))
         return IDBError(IDBExceptionCode::ConstraintError);
 
-    ASSERT(!m_objectStores.contains(info.identifier()));
+    ASSERT(!m_objectStoresByIdentifier.contains(info.identifier()));
     auto objectStore = MemoryObjectStore::create(info);
 
     m_databaseInfo->addExistingObjectStore(info);
@@ -128,7 +133,7 @@ IDBError MemoryIDBBackingStore::createObjectStore(const IDBResourceIdentifier& t
     ASSERT(rawTransaction->isVersionChange());
 
     rawTransaction->addNewObjectStore(*objectStore);
-    m_objectStores.set(info.identifier(), WTF::move(objectStore));
+    registerObjectStore(WTF::move(objectStore));
 
     return IDBError();
 }
@@ -137,20 +142,19 @@ void MemoryIDBBackingStore::removeObjectStoreForVersionChangeAbort(MemoryObjectS
 {
     LOG(IndexedDB, "MemoryIDBBackingStore::removeObjectStoreForVersionChangeAbort");
 
-    ASSERT(m_objectStores.contains(objectStore.info().identifier()));
-    ASSERT(m_objectStores.get(objectStore.info().identifier()) == &objectStore);
+    ASSERT(m_objectStoresByIdentifier.contains(objectStore.info().identifier()));
+    ASSERT(m_objectStoresByIdentifier.get(objectStore.info().identifier()) == &objectStore);
 
-    m_objectStores.remove(objectStore.info().identifier());
+    unregisterObjectStore(objectStore);
 }
 
-
 IDBError MemoryIDBBackingStore::keyExistsInObjectStore(const IDBResourceIdentifier&, uint64_t objectStoreIdentifier, const IDBKeyData& keyData, bool& keyExists)
 {
     LOG(IndexedDB, "MemoryIDBBackingStore::keyExistsInObjectStore");
 
     ASSERT(objectStoreIdentifier);
 
-    MemoryObjectStore* objectStore = m_objectStores.get(objectStoreIdentifier);
+    MemoryObjectStore* objectStore = m_objectStoresByIdentifier.get(objectStoreIdentifier);
     RELEASE_ASSERT(objectStore);
 
     keyExists = objectStore->containsRecord(keyData);
@@ -163,7 +167,7 @@ IDBError MemoryIDBBackingStore::deleteRecord(const IDBResourceIdentifier& transa
 
     ASSERT(objectStoreIdentifier);
 
-    MemoryObjectStore* objectStore = m_objectStores.get(objectStoreIdentifier);
+    MemoryObjectStore* objectStore = m_objectStoresByIdentifier.get(objectStoreIdentifier);
     RELEASE_ASSERT(objectStore);
     RELEASE_ASSERT(m_transactions.contains(transactionIdentifier));
 
@@ -181,7 +185,7 @@ IDBError MemoryIDBBackingStore::putRecord(const IDBResourceIdentifier& transacti
     if (!transaction)
         return IDBError(IDBExceptionCode::Unknown, WTF::ASCIILiteral("No backing store transaction found to get record"));
 
-    MemoryObjectStore* objectStore = m_objectStores.get(objectStoreIdentifier);
+    MemoryObjectStore* objectStore = m_objectStoresByIdentifier.get(objectStoreIdentifier);
     if (!objectStore)
         return IDBError(IDBExceptionCode::Unknown, WTF::ASCIILiteral("No backing store object store found to put record"));
 
@@ -198,7 +202,7 @@ IDBError MemoryIDBBackingStore::getRecord(const IDBResourceIdentifier& transacti
     if (!m_transactions.contains(transactionIdentifier))
         return IDBError(IDBExceptionCode::Unknown, WTF::ASCIILiteral("No backing store transaction found to get record"));
 
-    MemoryObjectStore* objectStore = m_objectStores.get(objectStoreIdentifier);
+    MemoryObjectStore* objectStore = m_objectStoresByIdentifier.get(objectStoreIdentifier);
     if (!objectStore)
         return IDBError(IDBExceptionCode::Unknown, WTF::ASCIILiteral("No backing store object store found"));
 
@@ -206,6 +210,25 @@ IDBError MemoryIDBBackingStore::getRecord(const IDBResourceIdentifier& transacti
     return IDBError();
 }
 
+void MemoryIDBBackingStore::registerObjectStore(std::unique_ptr<MemoryObjectStore>&& objectStore)
+{
+    ASSERT(objectStore);
+    ASSERT(!m_objectStoresByIdentifier.contains(objectStore->info().identifier()));
+    ASSERT(!m_objectStoresByName.contains(objectStore->info().name()));
+
+    m_objectStoresByName.set(objectStore->info().name(), objectStore.get());
+    m_objectStoresByIdentifier.set(objectStore->info().identifier(), WTF::move(objectStore));
+}
+
+void MemoryIDBBackingStore::unregisterObjectStore(MemoryObjectStore& objectStore)
+{
+    ASSERT(m_objectStoresByIdentifier.contains(objectStore.info().identifier()));
+    ASSERT(m_objectStoresByName.contains(objectStore.info().name()));
+
+    m_objectStoresByName.remove(objectStore.info().name());
+    m_objectStoresByIdentifier.remove(objectStore.info().identifier());
+}
+
 } // namespace IDBServer
 } // namespace WebCore
 
index 4318715..64b77ad 100644 (file)
@@ -67,7 +67,11 @@ private:
     std::unique_ptr<IDBDatabaseInfo> m_databaseInfo;
 
     HashMap<IDBResourceIdentifier, std::unique_ptr<MemoryBackingStoreTransaction>> m_transactions;
-    HashMap<uint64_t, std::unique_ptr<MemoryObjectStore>> m_objectStores;
+
+    void registerObjectStore(std::unique_ptr<MemoryObjectStore>&&);
+    void unregisterObjectStore(MemoryObjectStore&);
+    HashMap<uint64_t, std::unique_ptr<MemoryObjectStore>> m_objectStoresByIdentifier;
+    HashMap<String, MemoryObjectStore*> m_objectStoresByName;
 };
 
 } // namespace IDBServer
index 68ff5f1..41c2d5f 100644 (file)
@@ -171,6 +171,7 @@ void UniqueIDBDatabase::startVersionChangeTransaction()
     addOpenDatabaseConnection(*m_versionChangeDatabaseConnection);
 
     m_versionChangeTransaction = &m_versionChangeDatabaseConnection->createVersionChangeTransaction(requestedVersion);
+    m_inProgressTransactions.set(m_versionChangeTransaction->info().identifier(), m_versionChangeTransaction);
     m_server.postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::beginTransactionInBackingStore, m_versionChangeTransaction->info()));
 
     auto result = IDBResultData::openDatabaseUpgradeNeeded(operation->requestData().requestIdentifier(), *m_versionChangeTransaction);
@@ -391,16 +392,15 @@ void UniqueIDBDatabase::performCommitTransaction(uint64_t callbackIdentifier, co
     LOG(IndexedDB, "(db) UniqueIDBDatabase::performCommitTransaction");
 
     IDBError error = m_backingStore->commitTransaction(transactionIdentifier);
-    m_server.postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformCommitTransaction, callbackIdentifier, error));
+    m_server.postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformCommitTransaction, callbackIdentifier, error, transactionIdentifier));
 }
 
-void UniqueIDBDatabase::didPerformCommitTransaction(uint64_t callbackIdentifier, const IDBError& error)
+void UniqueIDBDatabase::didPerformCommitTransaction(uint64_t callbackIdentifier, const IDBError& error, const IDBResourceIdentifier& transactionIdentifier)
 {
     ASSERT(isMainThread());
     LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformCommitTransaction");
 
-    // Previously blocked transactions might now be unblocked.
-    invokeTransactionScheduler();
+    inProgressTransactionCompleted(transactionIdentifier);
 
     performErrorCallback(callbackIdentifier, error);
 }
@@ -430,7 +430,7 @@ void UniqueIDBDatabase::didPerformAbortTransaction(uint64_t callbackIdentifier,
     ASSERT(isMainThread());
     LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformAbortTransaction");
 
-    if (m_versionChangeTransaction || m_versionChangeTransaction->info().identifier() == transactionIdentifier) {
+    if (m_versionChangeTransaction && m_versionChangeTransaction->info().identifier() == transactionIdentifier) {
         ASSERT(&m_versionChangeTransaction->databaseConnection() == m_versionChangeDatabaseConnection);
         ASSERT(m_versionChangeTransaction->originalDatabaseInfo());
         m_databaseInfo = std::make_unique<IDBDatabaseInfo>(*m_versionChangeTransaction->originalDatabaseInfo());
@@ -439,8 +439,7 @@ void UniqueIDBDatabase::didPerformAbortTransaction(uint64_t callbackIdentifier,
         m_versionChangeDatabaseConnection = nullptr;
     }
 
-    // Previously blocked transactions might now be unblocked.
-    invokeTransactionScheduler();
+    inProgressTransactionCompleted(transactionIdentifier);
 
     performErrorCallback(callbackIdentifier, error);
 }
@@ -471,6 +470,17 @@ void UniqueIDBDatabase::connectionClosedFromClient(UniqueIDBDatabaseConnection&
     invokeTransactionScheduler();
 }
 
+void UniqueIDBDatabase::enqueueTransaction(Ref<UniqueIDBDatabaseTransaction>&& transaction)
+{
+    LOG(IndexedDB, "UniqueIDBDatabase::enqueueTransaction");
+
+    ASSERT(transaction->info().mode() != IndexedDB::TransactionMode::VersionChange);
+
+    m_pendingTransactions.append(WTF::move(transaction));
+
+    invokeTransactionScheduler();
+}
+
 void UniqueIDBDatabase::invokeTransactionScheduler()
 {
     if (!m_transactionSchedulingTimer.isActive())
@@ -481,12 +491,130 @@ void UniqueIDBDatabase::transactionSchedulingTimerFired()
 {
     LOG(IndexedDB, "(main) UniqueIDBDatabase::transactionSchedulingTimerFired");
 
-    if (!hasAnyOpenConnections() && m_versionChangeOperation) {
-        startVersionChangeTransaction();
-        return;
+    if (m_pendingTransactions.isEmpty()) {
+        if (!hasAnyOpenConnections() && m_versionChangeOperation) {
+            startVersionChangeTransaction();
+            return;
+        }
+    }
+
+    bool hadDeferredTransactions = false;
+    auto transaction = takeNextRunnableTransaction(hadDeferredTransactions);
+
+    if (transaction) {
+        m_inProgressTransactions.set(transaction->info().identifier(), transaction);
+        for (auto objectStore : transaction->objectStoreIdentifiers())
+            m_objectStoreTransactionCounts.add(objectStore);
+
+        activateTransactionInBackingStore(*transaction);
+
+        // If no transactions were deferred, it's possible we can start another transaction right now.
+        if (!hadDeferredTransactions)
+            invokeTransactionScheduler();
+    }
+}
+
+void UniqueIDBDatabase::activateTransactionInBackingStore(UniqueIDBDatabaseTransaction& transaction)
+{
+    LOG(IndexedDB, "(main) UniqueIDBDatabase::activateTransactionInBackingStore");
+
+    RefPtr<UniqueIDBDatabase> self(this);
+    RefPtr<UniqueIDBDatabaseTransaction> refTransaction(&transaction);
+
+    auto callback = [this, self, refTransaction](const IDBError& error) {
+        refTransaction->didActivateInBackingStore(error);
+    };
+
+    uint64_t callbackID = storeCallback(callback);
+    m_server.postDatabaseTask(createCrossThreadTask(*this, &UniqueIDBDatabase::performActivateTransactionInBackingStore, callbackID, transaction.info()));
+}
+
+void UniqueIDBDatabase::performActivateTransactionInBackingStore(uint64_t callbackIdentifier, const IDBTransactionInfo& info)
+{
+    LOG(IndexedDB, "(db) UniqueIDBDatabase::performActivateTransactionInBackingStore");
+
+    IDBError error = m_backingStore->beginTransaction(info);
+    m_server.postDatabaseTaskReply(createCrossThreadTask(*this, &UniqueIDBDatabase::didPerformActivateTransactionInBackingStore, callbackIdentifier, error));
+}
+
+void UniqueIDBDatabase::didPerformActivateTransactionInBackingStore(uint64_t callbackIdentifier, const IDBError& error)
+{
+    LOG(IndexedDB, "(main) UniqueIDBDatabase::didPerformActivateTransactionInBackingStore");
+
+    invokeTransactionScheduler();
+
+    performErrorCallback(callbackIdentifier, error);
+}
+
+template<typename T> bool scopesOverlap(const T& aScopes, const Vector<uint64_t>& bScopes)
+{
+    for (auto scope : bScopes) {
+        if (aScopes.contains(scope))
+            return true;
+    }
+
+    return false;
+}
+
+RefPtr<UniqueIDBDatabaseTransaction> UniqueIDBDatabase::takeNextRunnableTransaction(bool& hadDeferredTransactions)
+{
+    Deque<RefPtr<UniqueIDBDatabaseTransaction>> deferredTransactions;
+    RefPtr<UniqueIDBDatabaseTransaction> currentTransaction;
+
+    while (!m_pendingTransactions.isEmpty()) {
+        currentTransaction = m_pendingTransactions.takeFirst();
+
+        switch (currentTransaction->info().mode()) {
+        case IndexedDB::TransactionMode::ReadOnly:
+            // If there are any deferred transactions, the first one is a read-write transaction we need to unblock.
+            // Therefore, skip this read-only transaction if its scope overlaps with that read-write transaction.
+            if (!deferredTransactions.isEmpty()) {
+                ASSERT(deferredTransactions.first()->info().mode() == IndexedDB::TransactionMode::ReadWrite);
+                if (scopesOverlap(deferredTransactions.first()->objectStoreIdentifiers(), currentTransaction->objectStoreIdentifiers()))
+                    deferredTransactions.append(WTF::move(currentTransaction));
+            }
+
+            break;
+        case IndexedDB::TransactionMode::ReadWrite:
+            // If this read-write transaction's scope overlaps with running transactions, it must be deferred.
+            if (scopesOverlap(m_objectStoreTransactionCounts, currentTransaction->objectStoreIdentifiers()))
+                deferredTransactions.append(WTF::move(currentTransaction));
+
+            break;
+        case IndexedDB::TransactionMode::VersionChange:
+            // Version change transactions should never be scheduled in the traditional manner.
+            RELEASE_ASSERT_NOT_REACHED();
+        }
+
+        // If we didn't defer the currentTransaction above, it can be run now.
+        if (currentTransaction)
+            break;
     }
 
-    // FIXME: Handle starting other pending transactions here.
+    hadDeferredTransactions = !deferredTransactions.isEmpty();
+    if (!hadDeferredTransactions)
+        return WTF::move(currentTransaction);
+
+    // Prepend the deferred transactions back on the beginning of the deque for future scheduling passes.
+    while (!deferredTransactions.isEmpty())
+        m_pendingTransactions.prepend(deferredTransactions.takeLast());
+
+    return WTF::move(currentTransaction);
+}
+
+void UniqueIDBDatabase::inProgressTransactionCompleted(const IDBResourceIdentifier& transactionIdentifier)
+{
+    auto transaction = m_inProgressTransactions.take(transactionIdentifier);
+    ASSERT(transaction);
+
+    if (m_versionChangeTransaction == transaction)
+        m_versionChangeTransaction = nullptr;
+
+    for (auto objectStore : transaction->objectStoreIdentifiers())
+        m_objectStoreTransactionCounts.remove(objectStore);
+
+    // Previously blocked transactions might now be unblocked.
+    invokeTransactionScheduler();
 }
 
 void UniqueIDBDatabase::performErrorCallback(uint64_t callbackIdentifier, const IDBError& error)
index 2b390f0..f3b013e 100644 (file)
@@ -37,6 +37,7 @@
 #include "UniqueIDBDatabaseConnection.h"
 #include "UniqueIDBDatabaseTransaction.h"
 #include <wtf/Deque.h>
+#include <wtf/HashCountedSet.h>
 #include <wtf/HashSet.h>
 #include <wtf/Ref.h>
 #include <wtf/ThreadSafeRefCounted.h>
@@ -76,6 +77,8 @@ public:
     void transactionDestroyed(UniqueIDBDatabaseTransaction&);
     void connectionClosedFromClient(UniqueIDBDatabaseConnection&);
 
+    void enqueueTransaction(Ref<UniqueIDBDatabaseTransaction>&&);
+
 private:
     UniqueIDBDatabase(IDBServer&, const IDBDatabaseIdentifier&);
     
@@ -86,6 +89,9 @@ private:
     void startVersionChangeTransaction();
     void notifyConnectionsOfVersionChange();
 
+    void activateTransactionInBackingStore(UniqueIDBDatabaseTransaction&);
+    void inProgressTransactionCompleted(const IDBResourceIdentifier&);
+
     // Database thread operations
     void openBackingStore(const IDBDatabaseIdentifier&);
     void performCommitTransaction(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier);
@@ -94,14 +100,16 @@ private:
     void performCreateObjectStore(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, const IDBObjectStoreInfo&);
     void performPutOrAdd(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const IDBKeyData&, const ThreadSafeDataBuffer& valueData, IndexedDB::ObjectStoreOverwriteMode);
     void performGetRecord(uint64_t callbackIdentifier, const IDBResourceIdentifier& transactionIdentifier, uint64_t objectStoreIdentifier, const IDBKeyData&);
+    void performActivateTransactionInBackingStore(uint64_t callbackIdentifier, const IDBTransactionInfo&);
 
     // Main thread callbacks
     void didOpenBackingStore(const IDBDatabaseInfo&);
     void didPerformCreateObjectStore(uint64_t callbackIdentifier, const IDBError&, const IDBObjectStoreInfo&);
     void didPerformPutOrAdd(uint64_t callbackIdentifier, const IDBError&, const IDBKeyData&);
     void didPerformGetRecord(uint64_t callbackIdentifier, const IDBError&, const ThreadSafeDataBuffer&);
-    void didPerformCommitTransaction(uint64_t callbackIdentifier, const IDBError&);
+    void didPerformCommitTransaction(uint64_t callbackIdentifier, const IDBError&, const IDBResourceIdentifier& transactionIdentifier);
     void didPerformAbortTransaction(uint64_t callbackIdentifier, const IDBError&, const IDBResourceIdentifier& transactionIdentifier);
+    void didPerformActivateTransactionInBackingStore(uint64_t callbackIdentifier, const IDBError&);
 
     uint64_t storeCallback(ErrorCallback);
     uint64_t storeCallback(KeyDataCallback);
@@ -113,6 +121,7 @@ private:
 
     void invokeTransactionScheduler();
     void transactionSchedulingTimerFired();
+    RefPtr<UniqueIDBDatabaseTransaction> takeNextRunnableTransaction(bool& hadDeferredTransactions);
 
     IDBServer& m_server;
     IDBDatabaseIdentifier m_identifier;
@@ -134,6 +143,15 @@ private:
     HashMap<uint64_t, ValueDataCallback> m_valueDataCallbacks;
 
     Timer m_transactionSchedulingTimer;
+
+    Deque<RefPtr<UniqueIDBDatabaseTransaction>> m_pendingTransactions;
+    HashMap<IDBResourceIdentifier, RefPtr<UniqueIDBDatabaseTransaction>> m_inProgressTransactions;
+
+    // The key into this set is the object store ID.
+    // The set counts how many transactions are open to the given object store.
+    // This helps make sure opening narrowly scoped transactions (one or two object stores)
+    // doesn't continuously block widely scoped write transactions.
+    HashCountedSet<uint64_t> m_objectStoreTransactionCounts;
 };
 
 } // namespace IDBServer
index 5954251..9b39181 100644 (file)
@@ -93,6 +93,21 @@ UniqueIDBDatabaseTransaction& UniqueIDBDatabaseConnection::createVersionChangeTr
     return transaction.get();
 }
 
+void UniqueIDBDatabaseConnection::establishTransaction(const IDBTransactionInfo& info)
+{
+    LOG(IndexedDB, "UniqueIDBDatabaseConnection::establishTransaction");
+
+    ASSERT(info.mode() != IndexedDB::TransactionMode::VersionChange);
+
+    // No transactions should ever come from the client after the client has already told us
+    // the connection is closing.
+    ASSERT(!m_closePending);
+
+    Ref<UniqueIDBDatabaseTransaction> transaction = UniqueIDBDatabaseTransaction::create(*this, info);
+    m_transactionMap.set(transaction->info().identifier(), &transaction.get());
+    m_database.enqueueTransaction(WTF::move(transaction));
+}
+
 void UniqueIDBDatabaseConnection::didAbortTransaction(UniqueIDBDatabaseTransaction& transaction, const IDBError& error)
 {
     LOG(IndexedDB, "UniqueIDBDatabaseConnection::didAbortTransaction");
index 9c58657..29d7b10 100644 (file)
@@ -63,6 +63,7 @@ public:
     void fireVersionChangeEvent(uint64_t requestedVersion);
     UniqueIDBDatabaseTransaction& createVersionChangeTransaction(uint64_t newVersion);
 
+    void establishTransaction(const IDBTransactionInfo&);
     void didAbortTransaction(UniqueIDBDatabaseTransaction&, const IDBError&);
     void didCommitTransaction(UniqueIDBDatabaseTransaction&, const IDBError&);
     void didCreateObjectStore(const IDBResultData&);
index cbf6a6e..3a44dd2 100644 (file)
 namespace WebCore {
 namespace IDBServer {
 
-Ref<UniqueIDBDatabaseTransaction> UniqueIDBDatabaseTransaction::create(UniqueIDBDatabaseConnection& connection, IDBTransactionInfo& info)
+Ref<UniqueIDBDatabaseTransaction> UniqueIDBDatabaseTransaction::create(UniqueIDBDatabaseConnection& connection, const IDBTransactionInfo& info)
 {
     return adoptRef(*new UniqueIDBDatabaseTransaction(connection, info));
 }
 
-UniqueIDBDatabaseTransaction::UniqueIDBDatabaseTransaction(UniqueIDBDatabaseConnection& connection, IDBTransactionInfo& info)
+UniqueIDBDatabaseTransaction::UniqueIDBDatabaseTransaction(UniqueIDBDatabaseConnection& connection, const IDBTransactionInfo& info)
     : m_databaseConnection(connection)
     , m_transactionInfo(info)
 {
@@ -148,6 +148,32 @@ void UniqueIDBDatabaseTransaction::getRecord(const IDBRequestData& requestData,
     });
 }
 
+const Vector<uint64_t>& UniqueIDBDatabaseTransaction::objectStoreIdentifiers()
+{
+    if (!m_objectStoreIdentifiers.isEmpty())
+        return m_objectStoreIdentifiers;
+
+    auto& info = m_databaseConnection->database().info();
+    for (auto objectStoreName : info.objectStoreNames()) {
+        auto objectStoreInfo = info.infoForExistingObjectStore(objectStoreName);
+        ASSERT(objectStoreInfo);
+        if (!objectStoreInfo)
+            continue;
+
+        if (m_transactionInfo.objectStores().contains(objectStoreName))
+            m_objectStoreIdentifiers.append(objectStoreInfo->identifier());
+    }
+
+    return m_objectStoreIdentifiers;
+}
+
+void UniqueIDBDatabaseTransaction::didActivateInBackingStore(const IDBError& error)
+{
+    LOG(IndexedDB, "UniqueIDBDatabaseTransaction::didActivateInBackingStore");
+
+    m_databaseConnection->connectionToClient().didStartTransaction(m_transactionInfo.identifier(), error);
+}
+
 } // namespace IDBServer
 } // namespace WebCore
 
index a8e643d..a561dbc 100644 (file)
@@ -36,6 +36,7 @@
 namespace WebCore {
 
 class IDBDatabaseInfo;
+class IDBError;
 class IDBKeyData;
 class IDBObjectStoreInfo;
 class IDBRequestData;
@@ -47,7 +48,7 @@ class UniqueIDBDatabaseConnection;
 
 class UniqueIDBDatabaseTransaction : public RefCounted<UniqueIDBDatabaseTransaction> {
 public:
-    static Ref<UniqueIDBDatabaseTransaction> create(UniqueIDBDatabaseConnection&, IDBTransactionInfo&);
+    static Ref<UniqueIDBDatabaseTransaction> create(UniqueIDBDatabaseConnection&, const IDBTransactionInfo&);
 
     ~UniqueIDBDatabaseTransaction();
 
@@ -65,13 +66,19 @@ public:
     void putOrAdd(const IDBRequestData&, const IDBKeyData&, const ThreadSafeDataBuffer& valueData, IndexedDB::ObjectStoreOverwriteMode);
     void getRecord(const IDBRequestData&, const IDBKeyData&);
 
+    void didActivateInBackingStore(const IDBError&);
+
+    const Vector<uint64_t>& objectStoreIdentifiers();
+
 private:
-    UniqueIDBDatabaseTransaction(UniqueIDBDatabaseConnection&, IDBTransactionInfo&);
+    UniqueIDBDatabaseTransaction(UniqueIDBDatabaseConnection&, const IDBTransactionInfo&);
 
     Ref<UniqueIDBDatabaseConnection> m_databaseConnection;
     IDBTransactionInfo m_transactionInfo;
 
     std::unique_ptr<IDBDatabaseInfo> m_originalDatabaseInfo;
+
+    Vector<uint64_t> m_objectStoreIdentifiers;
 };
 
 } // namespace IDBServer
index 75a46e6..91da853 100644 (file)
@@ -35,6 +35,15 @@ IDBTransactionInfo::IDBTransactionInfo(const IDBResourceIdentifier& identifier)
 {
 }
 
+IDBTransactionInfo IDBTransactionInfo::clientTransaction(const IDBClient::IDBConnectionToServer& connection, const Vector<String>& objectStores, IndexedDB::TransactionMode mode)
+{
+    IDBTransactionInfo result((IDBResourceIdentifier(connection)));
+    result.m_mode = mode;
+    result.m_objectStores = objectStores;
+
+    return result;
+}
+
 IDBTransactionInfo IDBTransactionInfo::versionChange(const IDBServer::IDBConnectionToClient& connection, uint64_t newVersion)
 {
     IDBTransactionInfo result((IDBResourceIdentifier(connection)));
@@ -50,6 +59,10 @@ IDBTransactionInfo IDBTransactionInfo::isolatedCopy() const
     result.m_mode = m_mode;
     result.m_newVersion = m_newVersion;
 
+    result.m_objectStores.reserveCapacity(m_objectStores.size());
+    for (auto& objectStore : m_objectStores)
+        result.m_objectStores.uncheckedAppend(objectStore.isolatedCopy());
+
     return WTF::move(result);
 }
 
index b34abd9..19f9c57 100644 (file)
 #include <wtf/Vector.h>
 
 namespace WebCore {
+
+namespace IDBClient {
+class IDBConnectionToServer;
+}
+
 namespace IDBServer {
 class IDBConnectionToClient;
 }
 
 class IDBTransactionInfo {
 public:
+    static IDBTransactionInfo clientTransaction(const IDBClient::IDBConnectionToServer&, const Vector<String>& objectStores, IndexedDB::TransactionMode);
     static IDBTransactionInfo versionChange(const IDBServer::IDBConnectionToClient&, uint64_t newVersion);
 
     IDBTransactionInfo isolatedCopy() const;
index 8b606b9..c5de067 100644 (file)
@@ -188,6 +188,15 @@ void InProcessIDBServer::getRecord(const IDBRequestData& requestData, IDBKey* ke
     });
 }
 
+void InProcessIDBServer::establishTransaction(uint64_t databaseConnectionIdentifier, const IDBTransactionInfo& info)
+{
+    RefPtr<InProcessIDBServer> self(this);
+
+    RunLoop::current().dispatch([this, self, databaseConnectionIdentifier, info] {
+        m_server->establishTransaction(databaseConnectionIdentifier, info);
+    });
+}
+
 void InProcessIDBServer::fireVersionChangeEvent(IDBServer::UniqueIDBDatabaseConnection& connection, uint64_t requestedVersion)
 {
     RefPtr<InProcessIDBServer> self(this);
@@ -197,6 +206,14 @@ void InProcessIDBServer::fireVersionChangeEvent(IDBServer::UniqueIDBDatabaseConn
     });
 }
 
+void InProcessIDBServer::didStartTransaction(const IDBResourceIdentifier& transactionIdentifier, const IDBError& error)
+{
+    RefPtr<InProcessIDBServer> self(this);
+    RunLoop::current().dispatch([this, self, transactionIdentifier, error] {
+        m_connectionToServer->didStartTransaction(transactionIdentifier, error);
+    });
+}
+
 void InProcessIDBServer::databaseConnectionClosed(uint64_t databaseConnectionIdentifier)
 {
     RefPtr<InProcessIDBServer> self(this);
index efc31a0..d6f28a4 100644 (file)
@@ -61,6 +61,7 @@ public:
     virtual void createObjectStore(const IDBRequestData&, const IDBObjectStoreInfo&) override final;
     virtual void putOrAdd(const IDBRequestData&, IDBKey*, SerializedScriptValue&, const IndexedDB::ObjectStoreOverwriteMode) override final;
     virtual void getRecord(const IDBRequestData&, IDBKey*) override final;
+    virtual void establishTransaction(uint64_t databaseConnectionIdentifier, const IDBTransactionInfo&) override final;
     virtual void databaseConnectionClosed(uint64_t databaseConnectionIdentifier) override final;
 
     // IDBConnectionToClient
@@ -73,6 +74,7 @@ public:
     virtual void didPutOrAdd(const IDBResultData&) override final;
     virtual void didGetRecord(const IDBResultData&) override final;
     virtual void fireVersionChangeEvent(IDBServer::UniqueIDBDatabaseConnection&, uint64_t requestedVersion) override final;
+    virtual void didStartTransaction(const IDBResourceIdentifier& transactionIdentifier, const IDBError&) override final;
 
     virtual void ref() override { RefCounted<InProcessIDBServer>::ref(); }
     virtual void deref() override { RefCounted<InProcessIDBServer>::deref(); }
index 3bf4b29..08af04c 100644 (file)
@@ -346,6 +346,9 @@ Deprecated::ScriptValue deserializeIDBValueData(ScriptExecutionContext& context,
     DOMRequestState state(&context);
     auto* execState = state.exec();
 
+    if (!execState)
+        return Deprecated::ScriptValue();
+
     if (!valueData.data())
         return Deprecated::ScriptValue(execState->vm(), jsUndefined());
 
index e53508f..9dd401d 100644 (file)
@@ -81,6 +81,43 @@ JSValue JSIDBDatabase::createObjectStore(ExecState& state)
     return result;
 }
 
+JSValue JSIDBDatabase::transaction(ExecState& exec)
+{
+    size_t argsCount = std::min<size_t>(2, exec.argumentCount());
+    if (argsCount < 1)
+        return exec.vm().throwException(&exec, createNotEnoughArgumentsError(&exec));
+
+    auto* scriptContext = jsCast<JSDOMGlobalObject*>(exec.lexicalGlobalObject())->scriptExecutionContext();
+    if (!scriptContext)
+        return jsUndefined();
+
+    Vector<String> scope;
+    JSValue scopeArg(exec.argument(0));
+    if (scopeArg.isObject() && isJSArray(scopeArg)) {
+        scope = toNativeArray<String>(&exec, scopeArg);
+        if (exec.hadException())
+            return jsUndefined();
+    } else {
+        scope.append(scopeArg.toString(&exec)->value(&exec));
+        if (exec.hadException())
+            return jsUndefined();
+    }
+
+    String mode;
+    if (argsCount == 2) {
+        JSValue modeArg(exec.argument(1));
+        mode = modeArg.toString(&exec)->value(&exec);
+
+        if (exec.hadException())
+            return jsUndefined();
+    }
+
+    ExceptionCode ec = 0;
+    JSValue result = toJS(&exec, globalObject(), impl().transaction(scriptContext, scope, mode, ec).get());
+    setDOMException(&exec, ec);
+    return result;
+}
+
 }
 
 #endif
index 732c7cc..ae3c729 100644 (file)
@@ -92,7 +92,7 @@ JSC::ExecState* execStateFromNode(DOMWrapperWorld& world, Node* node)
 
 JSC::ExecState* execStateFromPage(DOMWrapperWorld& world, Page* page)
 {
-    return page->mainFrame().script().globalObject(world)->globalExec();
+    return page ? page->mainFrame().script().globalObject(world)->globalExec() : nullptr;
 }
 
 JSC::ExecState* execStateFromWorkerGlobalScope(WorkerGlobalScope* workerGlobalScope)