LibWeb: Prevent failing IDB requests from clobbering previous ones
If one request on a transaction succeeds, then the next one fails, that would cause the abort algorithm to run before the success for the first request due to the task queue ordering. Instead, queue the processing for the next request after the completion of the current request.
This commit is contained in:
parent
2c48aa0b67
commit
5ff1ae1876
4 changed files with 63 additions and 6 deletions
|
|
@ -1315,9 +1315,6 @@ GC::Ref<IDBRequest> asynchronously_execute_a_request(JS::Realm& realm, IDBReques
|
|||
// 5. Set request’s processed flag to true.
|
||||
request->set_processed(true);
|
||||
|
||||
// Allow the next operation in the queue to proceed.
|
||||
transaction->request_list().on_request_processed();
|
||||
|
||||
// 6. Queue a database task to run these steps:
|
||||
dbgln_if(IDB_DEBUG, "asynchronously_execute_a_request: step 5.6: request finished without error, queuing task to finish up");
|
||||
queue_a_database_task(GC::create_function(realm.vm().heap(), [&realm, request, result, transaction]() mutable {
|
||||
|
|
@ -1362,6 +1359,11 @@ GC::Ref<IDBRequest> asynchronously_execute_a_request(JS::Realm& realm, IDBReques
|
|||
// be fired before the transaction can be committed.
|
||||
transaction->request_list().check_all_processed();
|
||||
}));
|
||||
|
||||
// Allow the next operation in the queue to proceed. This runs after the above task to ensure that if another
|
||||
// request is ready, it will run after the success or failure events are fired. Otherwise, the next request may
|
||||
// throw an error and clobber the result of this request.
|
||||
transaction->request_list().on_request_processed();
|
||||
}));
|
||||
|
||||
// 6. Return request.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
put:success
|
||||
add:error
|
||||
tx:error
|
||||
tx:abort
|
||||
|
|
@ -2,8 +2,7 @@ Harness status: OK
|
|||
|
||||
Found 12 tests
|
||||
|
||||
11 Pass
|
||||
1 Fail
|
||||
12 Pass
|
||||
Pass Explicitly committed data can be read back out.
|
||||
Pass commit() on a version change transaction does not cause errors.
|
||||
Pass A committed transaction becomes inactive immediately.
|
||||
|
|
@ -14,5 +13,5 @@ Pass Calling commit on a committed transaction throws.
|
|||
Pass Calling abort on a committed transaction throws and does not prevent persisting the data.
|
||||
Pass Calling txn.commit() when txn is inactive should throw.
|
||||
Pass Transactions with same scope should stay in program order, even if one calls commit.
|
||||
Fail Transactions that explicitly commit and have errors should abort.
|
||||
Pass Transactions that explicitly commit and have errors should abort.
|
||||
Pass Transactions that handle all errors properly should behave as expected when an explicit commit is called in an onerror handler.
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<!DOCTYPE html>
|
||||
<script src="include.js"></script>
|
||||
<script>
|
||||
asyncTest(done => {
|
||||
setTimeout(() => {
|
||||
spoofCurrentURL("https://example.com/");
|
||||
|
||||
const openReq = indexedDB.open("commit-error-event-order", 1);
|
||||
openReq.onupgradeneeded = (e) => {
|
||||
const db = e.target.result;
|
||||
db.createObjectStore("store", { keyPath: "key" });
|
||||
};
|
||||
openReq.onsuccess = (e) => {
|
||||
const db = e.target.result;
|
||||
|
||||
// First transaction: insert key "one"
|
||||
const txn1 = db.transaction("store", "readwrite");
|
||||
txn1.objectStore("store").add({ key: "one", value: "first" });
|
||||
txn1.oncomplete = () => {
|
||||
// Second transaction: put + add(duplicate) + commit
|
||||
const txn2 = db.transaction("store", "readwrite");
|
||||
const store2 = txn2.objectStore("store");
|
||||
|
||||
const putReq = store2.put({ key: "two", value: "second" });
|
||||
putReq.onsuccess = () => println("put:success");
|
||||
putReq.onerror = () => println("put:error");
|
||||
|
||||
const addReq = store2.add({ key: "one", value: "duplicate" });
|
||||
addReq.onsuccess = () => println("add:success");
|
||||
addReq.onerror = (ev) => {
|
||||
println("add:error");
|
||||
ev.preventDefault();
|
||||
};
|
||||
|
||||
txn2.addEventListener("error", () => println("tx:error"));
|
||||
txn2.addEventListener("abort", () => {
|
||||
println("tx:abort");
|
||||
db.close();
|
||||
done();
|
||||
});
|
||||
txn2.addEventListener("complete", () => {
|
||||
println("tx:complete");
|
||||
db.close();
|
||||
done();
|
||||
});
|
||||
|
||||
txn2.commit();
|
||||
};
|
||||
};
|
||||
}, 0);
|
||||
});
|
||||
</script>
|
||||
Loading…
Reference in a new issue