+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
+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
-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
--- /dev/null
+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()
--- /dev/null
+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>
--- /dev/null
+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.
--- /dev/null
+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>
--- /dev/null
+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.
--- /dev/null
+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
--- /dev/null
+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.
+
--- /dev/null
+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>
--- /dev/null
+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.
+
--- /dev/null
+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>
--- /dev/null
+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.
+
--- /dev/null
+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>
--- /dev/null
+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.
+
--- /dev/null
+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>
+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
[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;
-
};
const unsigned TransactionModeMaximum = 2;
enum class TransactionState {
- Unstarted,
Active,
Inactive,
Committing,
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");
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");
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
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&);
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;
class IDBObjectStoreInfo;
class IDBRequestData;
class IDBResourceIdentifier;
+class IDBTransactionInfo;
class SerializedScriptValue;
namespace IndexedDB {
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;
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&)
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");
uint64_t databaseConnectionIdentifier() const { return m_databaseConnectionIdentifier; }
Ref<IDBTransaction> startVersionChangeTransaction(const IDBTransactionInfo&);
+ void didStartTransaction(IDBTransaction&);
+
void commitTransaction(IDBTransaction&);
void didCommitTransaction(IDBTransaction&);
void abortTransaction(IDBTransaction&);
, 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()
{
LOG(IndexedDB, "IDBTransaction::operationTimerFired");
- if (m_state == IndexedDB::TransactionState::Unstarted)
+ if (!m_startedOnServer)
return;
if (!m_transactionOperationQueue.isEmpty()) {
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");
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();
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&);
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;
m_delegate->fireVersionChangeEvent(connection, requestedVersion);
}
+void IDBConnectionToClient::didStartTransaction(const IDBResourceIdentifier& transactionIdentifier, const IDBError& error)
+{
+ m_delegate->didStartTransaction(transactionIdentifier, error);
+}
+
} // namespace IDBServer
} // namespace WebCore
void didGetRecord(const IDBResultData&);
void fireVersionChangeEvent(UniqueIDBDatabaseConnection&, uint64_t requestedVersion);
+ void didStartTransaction(const IDBResourceIdentifier& transactionIdentifier, const IDBError&);
private:
IDBConnectionToClient(IDBConnectionToClientDelegate&);
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;
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");
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>&&);
// 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));
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);
ASSERT(rawTransaction->isVersionChange());
rawTransaction->addNewObjectStore(*objectStore);
- m_objectStores.set(info.identifier(), WTF::move(objectStore));
+ registerObjectStore(WTF::move(objectStore));
return IDBError();
}
{
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);
ASSERT(objectStoreIdentifier);
- MemoryObjectStore* objectStore = m_objectStores.get(objectStoreIdentifier);
+ MemoryObjectStore* objectStore = m_objectStoresByIdentifier.get(objectStoreIdentifier);
RELEASE_ASSERT(objectStore);
RELEASE_ASSERT(m_transactions.contains(transactionIdentifier));
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"));
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"));
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
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
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);
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);
}
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());
m_versionChangeDatabaseConnection = nullptr;
}
- // Previously blocked transactions might now be unblocked.
- invokeTransactionScheduler();
+ inProgressTransactionCompleted(transactionIdentifier);
performErrorCallback(callbackIdentifier, error);
}
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())
{
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)
#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>
void transactionDestroyed(UniqueIDBDatabaseTransaction&);
void connectionClosedFromClient(UniqueIDBDatabaseConnection&);
+ void enqueueTransaction(Ref<UniqueIDBDatabaseTransaction>&&);
+
private:
UniqueIDBDatabase(IDBServer&, const IDBDatabaseIdentifier&);
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);
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);
void invokeTransactionScheduler();
void transactionSchedulingTimerFired();
+ RefPtr<UniqueIDBDatabaseTransaction> takeNextRunnableTransaction(bool& hadDeferredTransactions);
IDBServer& m_server;
IDBDatabaseIdentifier m_identifier;
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
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");
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&);
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)
{
});
}
+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
namespace WebCore {
class IDBDatabaseInfo;
+class IDBError;
class IDBKeyData;
class IDBObjectStoreInfo;
class IDBRequestData;
class UniqueIDBDatabaseTransaction : public RefCounted<UniqueIDBDatabaseTransaction> {
public:
- static Ref<UniqueIDBDatabaseTransaction> create(UniqueIDBDatabaseConnection&, IDBTransactionInfo&);
+ static Ref<UniqueIDBDatabaseTransaction> create(UniqueIDBDatabaseConnection&, const IDBTransactionInfo&);
~UniqueIDBDatabaseTransaction();
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
{
}
+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)));
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);
}
#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;
});
}
+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);
});
}
+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);
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
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(); }
DOMRequestState state(&context);
auto* execState = state.exec();
+ if (!execState)
+ return Deprecated::ScriptValue();
+
if (!valueData.data())
return Deprecated::ScriptValue(execState->vm(), jsUndefined());
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
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)