LibWeb/IndexedDB: Remove spin_until from request processing

This commit is contained in:
Luke Wilde 2025-09-09 20:41:59 +01:00 committed by Andreas Kling
parent 6d43e3cc93
commit 5c69784ef9
Notes: github-actions[bot] 2025-10-08 15:26:51 +00:00
16 changed files with 559 additions and 341 deletions

View file

@ -1,5 +1,6 @@
/*
* Copyright (c) 2024-2025, stelar7 <dudedbz@gmail.com>
* Copyright (c) 2025, Luke Wilde <luke@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -19,7 +20,6 @@
#include <LibWeb/FileAPI/Blob.h>
#include <LibWeb/FileAPI/File.h>
#include <LibWeb/HTML/EventNames.h>
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
#include <LibWeb/HTML/StructuredSerialize.h>
#include <LibWeb/IndexedDB/IDBCursor.h>
#include <LibWeb/IndexedDB/IDBDatabase.h>
@ -49,7 +49,7 @@ constexpr double const MAX_KEY_GENERATOR_VALUE { __builtin_exp2(53) };
#endif
// https://w3c.github.io/IndexedDB/#open-a-database-connection
WebIDL::ExceptionOr<GC::Ref<IDBDatabase>> open_a_database_connection(JS::Realm& realm, StorageAPI::StorageKey storage_key, String name, Optional<u64> maybe_version, GC::Ref<IDBRequest> request)
void open_a_database_connection(JS::Realm& realm, StorageAPI::StorageKey storage_key, String name, Optional<u64> maybe_version, GC::Ref<IDBRequest> request, GC::Ref<GC::Function<void(WebIDL::ExceptionOr<GC::Ref<IDBDatabase>>)>> on_complete)
{
// 1. Let queue be the connection queue for storageKey and name.
auto& queue = ConnectionQueueHandler::for_key_and_name(storage_key, name);
@ -59,128 +59,131 @@ WebIDL::ExceptionOr<GC::Ref<IDBDatabase>> open_a_database_connection(JS::Realm&
dbgln_if(IDB_DEBUG, "open_a_database_connection: added request {} to queue", request->uuid());
// 3. Wait until all previous requests in queue have been processed.
HTML::main_thread_event_loop().spin_until(GC::create_function(realm.vm().heap(), [queue, request]() {
if constexpr (IDB_DEBUG) {
dbgln("open_a_database_connection: waiting for step 3");
dbgln("requests in queue:");
for (auto const& item : queue) {
dbgln("[{}] - {} = {}", item == request ? "x"sv : " "sv, item->uuid(), item->processed() ? "processed"sv : "not processed"sv);
if constexpr (IDB_DEBUG) {
dbgln("open_a_database_connection: waiting for step 3");
dbgln("requests in queue:");
for (auto const& item : queue) {
dbgln("[{}] - {} = {}", item == request ? "x"sv : " "sv, item->uuid(), item->processed() ? "processed"sv : "not processed"sv);
}
}
queue.all_previous_requests_processed(realm.heap(), request, GC::create_function(realm.heap(), [&realm, storage_key = move(storage_key), name = move(name), maybe_version = move(maybe_version), request, on_complete] -> void {
// 4. Let db be the database named name in storageKey, or null otherwise.
GC::Ptr<Database> db;
auto maybe_db = Database::for_key_and_name(storage_key, name);
if (maybe_db.has_value()) {
db = maybe_db.value();
}
// 5. If version is undefined, let version be 1 if db is null, or dbs version otherwise.
auto version = maybe_version.value_or(maybe_db.has_value() ? maybe_db.value()->version() : 1);
// 6. If db is null, let db be a new database with name name, version 0 (zero), and with no object stores.
// If this fails for any reason, return an appropriate error (e.g. a "QuotaExceededError" or "UnknownError" DOMException).
if (!maybe_db.has_value()) {
auto maybe_database = Database::create_for_key_and_name(realm, storage_key, name);
if (maybe_database.is_error()) {
on_complete->function()(WebIDL::OperationError::create(realm, "Unable to create a new database"_utf16));
return;
}
db = maybe_database.release_value();
}
return queue.all_previous_requests_processed(request);
}));
// 4. Let db be the database named name in storageKey, or null otherwise.
GC::Ptr<Database> db;
auto maybe_db = Database::for_key_and_name(storage_key, name);
if (maybe_db.has_value()) {
db = maybe_db.value();
}
// 5. If version is undefined, let version be 1 if db is null, or dbs version otherwise.
auto version = maybe_version.value_or(maybe_db.has_value() ? maybe_db.value()->version() : 1);
// 6. If db is null, let db be a new database with name name, version 0 (zero), and with no object stores.
// If this fails for any reason, return an appropriate error (e.g. a "QuotaExceededError" or "UnknownError" DOMException).
if (!maybe_db.has_value()) {
auto maybe_database = Database::create_for_key_and_name(realm, storage_key, name);
if (maybe_database.is_error()) {
return WebIDL::OperationError::create(realm, "Unable to create a new database"_utf16);
// 7. If dbs version is greater than version, return a newly created "VersionError" DOMException and abort these steps.
if (db->version() > version) {
on_complete->function()(WebIDL::VersionError::create(realm, "Database version is greater than the requested version"_utf16));
return;
}
db = maybe_database.release_value();
}
// 8. Let connection be a new connection to db.
auto connection = IDBDatabase::create(realm, *db);
dbgln_if(IDB_DEBUG, "Created new connection with UUID: {}", connection->uuid());
// 7. If dbs version is greater than version, return a newly created "VersionError" DOMException and abort these steps.
if (db->version() > version) {
return WebIDL::VersionError::create(realm, "Database version is greater than the requested version"_utf16);
}
// 9. Set connections version to version.
connection->set_version(version);
// 8. Let connection be a new connection to db.
auto connection = IDBDatabase::create(realm, *db);
dbgln_if(IDB_DEBUG, "Created new connection with UUID: {}", connection->uuid());
// 10. If dbs version is less than version, then:
if (db->version() < version) {
// 1. Let openConnections be the set of all connections, except connection, associated with db.
auto open_connections = db->associated_connections_except(connection);
// 9. Set connections version to version.
connection->set_version(version);
// 10. If dbs version is less than version, then:
if (db->version() < version) {
// 1. Let openConnections be the set of all connections, except connection, associated with db.
auto open_connections = db->associated_connections_except(connection);
// 2. For each entry of openConnections that does not have its close pending flag set to true,
// queue a database task to fire a version change event named versionchange at entry with dbs version and version.
IGNORE_USE_IN_ESCAPING_LAMBDA u32 events_to_fire = open_connections.size();
IGNORE_USE_IN_ESCAPING_LAMBDA u32 events_fired = 0;
for (auto const& entry : open_connections) {
if (!entry->close_pending()) {
queue_a_database_task(GC::create_function(realm.vm().heap(), [&realm, entry, db, version, &events_fired]() {
fire_a_version_change_event(realm, HTML::EventNames::versionchange, *entry, db->version(), version);
// 2. For each entry of openConnections that does not have its close pending flag set to true,
// queue a database task to fire a version change event named versionchange at entry with dbs version and version.
IGNORE_USE_IN_ESCAPING_LAMBDA u32 events_to_fire = open_connections.size();
IGNORE_USE_IN_ESCAPING_LAMBDA u32 events_fired = 0;
for (auto const& entry : open_connections) {
if (!entry->close_pending()) {
queue_a_database_task(GC::create_function(realm.vm().heap(), [&realm, entry, db, version, &events_fired]() {
fire_a_version_change_event(realm, HTML::EventNames::versionchange, *entry, db->version(), version);
events_fired++;
}));
} else {
events_fired++;
}));
} else {
events_fired++;
}
}
// 3. Wait for all of the events to be fired.
HTML::main_thread_event_loop().spin_until(GC::create_function(realm.vm().heap(), [&events_to_fire, &events_fired]() {
if constexpr (IDB_DEBUG) {
dbgln("open_a_database_connection: waiting for step 10.3");
dbgln("events_fired: {}, events_to_fire: {}", events_fired, events_to_fire);
}
return events_fired == events_to_fire;
}));
// 4. If any of the connections in openConnections are still not closed,
// queue a database task to fire a version change event named blocked at request with dbs version and version.
for (auto const& entry : open_connections) {
if (entry->state() != IDBDatabase::ConnectionState::Closed) {
queue_a_database_task(GC::create_function(realm.vm().heap(), [&realm, entry, db, version]() {
fire_a_version_change_event(realm, HTML::EventNames::blocked, *entry, db->version(), version);
}));
}
}
// 5. Wait until all connections in openConnections are closed.
HTML::main_thread_event_loop().spin_until(GC::create_function(realm.vm().heap(), [open_connections]() {
if constexpr (IDB_DEBUG) {
dbgln("open_a_database_connection: waiting for step 10.5");
dbgln("open connections: {}", open_connections.size());
for (auto const& connection : open_connections) {
dbgln(" - {}", connection->uuid());
}
}
// 3. Wait for all of the events to be fired.
HTML::main_thread_event_loop().spin_until(GC::create_function(realm.vm().heap(), [&events_to_fire, &events_fired]() {
if constexpr (IDB_DEBUG) {
dbgln("open_a_database_connection: waiting for step 10.3");
dbgln("events_fired: {}, events_to_fire: {}", events_fired, events_to_fire);
}
return events_fired == events_to_fire;
}));
// 4. If any of the connections in openConnections are still not closed,
// queue a database task to fire a version change event named blocked at request with dbs version and version.
for (auto const& entry : open_connections) {
if (entry->state() != IDBDatabase::ConnectionState::Closed) {
return false;
queue_a_database_task(GC::create_function(realm.vm().heap(), [&realm, entry, db, version]() {
fire_a_version_change_event(realm, HTML::EventNames::blocked, *entry, db->version(), version);
}));
}
}
return true;
}));
// 5. Wait until all connections in openConnections are closed.
HTML::main_thread_event_loop().spin_until(GC::create_function(realm.vm().heap(), [open_connections]() {
if constexpr (IDB_DEBUG) {
dbgln("open_a_database_connection: waiting for step 10.5");
dbgln("open connections: {}", open_connections.size());
for (auto const& connection : open_connections) {
dbgln(" - {}", connection->uuid());
}
}
// 6. Run upgrade a database using connection, version and request.
upgrade_a_database(realm, connection, version, request);
for (auto const& entry : open_connections) {
if (entry->state() != IDBDatabase::ConnectionState::Closed) {
return false;
}
}
// 7. If connection was closed, return a newly created "AbortError" DOMException and abort these steps.
if (connection->state() == IDBDatabase::ConnectionState::Closed)
return WebIDL::AbortError::create(realm, "Connection was closed"_utf16);
return true;
}));
// 8. If request's error is set, run the steps to close a database connection with connection,
// return a newly created "AbortError" DOMException and abort these steps.
if (request->has_error()) {
close_a_database_connection(*connection);
return WebIDL::AbortError::create(realm, "Upgrade transaction was aborted"_utf16);
// 6. Run upgrade a database using connection, version and request.
upgrade_a_database(realm, connection, version, request);
// 7. If connection was closed, return a newly created "AbortError" DOMException and abort these steps.
if (connection->state() == IDBDatabase::ConnectionState::Closed) {
on_complete->function()(WebIDL::AbortError::create(realm, "Connection was closed"_utf16));
return;
}
// 8. If request's error is set, run the steps to close a database connection with connection,
// return a newly created "AbortError" DOMException and abort these steps.
if (request->has_error()) {
close_a_database_connection(*connection);
on_complete->function()(WebIDL::AbortError::create(realm, "Upgrade transaction was aborted"_utf16));
return;
}
}
}
// 11. Return connection.
return connection;
// 11. Return connection.
on_complete->function()(connection);
}));
}
bool fire_a_version_change_event(JS::Realm& realm, FlyString const& event_name, GC::Ref<DOM::EventTarget> target, u64 old_version, Optional<u64> new_version)
@ -434,7 +437,7 @@ void upgrade_a_database(JS::Realm& realm, GC::Ref<IDBDatabase> connection, u64 v
}
// https://w3c.github.io/IndexedDB/#deleting-a-database
WebIDL::ExceptionOr<u64> delete_a_database(JS::Realm& realm, StorageAPI::StorageKey storage_key, String name, GC::Ref<IDBRequest> request)
void delete_a_database(JS::Realm& realm, StorageAPI::StorageKey storage_key, String name, GC::Ref<IDBRequest> request, GC::Ref<GC::Function<void(WebIDL::ExceptionOr<u64>)>> on_complete)
{
// 1. Let queue be the connection queue for storageKey and name.
auto& queue = ConnectionQueueHandler::for_key_and_name(storage_key, name);
@ -444,91 +447,93 @@ WebIDL::ExceptionOr<u64> delete_a_database(JS::Realm& realm, StorageAPI::Storage
dbgln_if(IDB_DEBUG, "delete_a_database: added request {} to queue", request->uuid());
// 3. Wait until all previous requests in queue have been processed.
HTML::main_thread_event_loop().spin_until(GC::create_function(realm.vm().heap(), [queue, request]() {
if constexpr (IDB_DEBUG) {
dbgln("delete_a_database: waiting for step 3");
dbgln("requests in queue:");
for (auto const& item : queue) {
dbgln("[{}] - {} = {}", item == request ? "x"sv : " "sv, item->uuid(), item->processed() ? "processed"sv : "not processed"sv);
}
if constexpr (IDB_DEBUG) {
dbgln("delete_a_database: waiting for step 3");
dbgln("requests in queue:");
for (auto const& item : queue) {
dbgln("[{}] - {} = {}", item == request ? "x"sv : " "sv, item->uuid(), item->processed() ? "processed"sv : "not processed"sv);
}
}
queue.all_previous_requests_processed(realm.heap(), request, GC::create_function(realm.heap(), [&realm, storage_key = move(storage_key), name = move(name), on_complete] -> void {
// 4. Let db be the database named name in storageKey, if one exists. Otherwise, return 0 (zero).
auto maybe_db = Database::for_key_and_name(storage_key, name);
if (!maybe_db.has_value()) {
on_complete->function()(0);
return;
}
return queue.all_previous_requests_processed(request);
}));
auto db = maybe_db.value();
// 4. Let db be the database named name in storageKey, if one exists. Otherwise, return 0 (zero).
auto maybe_db = Database::for_key_and_name(storage_key, name);
if (!maybe_db.has_value())
return 0;
// 5. Let openConnections be the set of all connections associated with db.
auto open_connections = db->associated_connections();
auto db = maybe_db.value();
// 5. Let openConnections be the set of all connections associated with db.
auto open_connections = db->associated_connections();
// 6. For each entry of openConnections that does not have its close pending flag set to true,
// queue a database task to fire a version change event named versionchange at entry with dbs version and null.
IGNORE_USE_IN_ESCAPING_LAMBDA u32 events_to_fire = open_connections.size();
IGNORE_USE_IN_ESCAPING_LAMBDA u32 events_fired = 0;
for (auto const& entry : open_connections) {
if (!entry->close_pending()) {
queue_a_database_task(GC::create_function(realm.vm().heap(), [&realm, entry, db, &events_fired]() {
fire_a_version_change_event(realm, HTML::EventNames::versionchange, *entry, db->version(), {});
// 6. For each entry of openConnections that does not have its close pending flag set to true,
// queue a database task to fire a version change event named versionchange at entry with dbs version and null.
IGNORE_USE_IN_ESCAPING_LAMBDA u32 events_to_fire = open_connections.size();
IGNORE_USE_IN_ESCAPING_LAMBDA u32 events_fired = 0;
for (auto const& entry : open_connections) {
if (!entry->close_pending()) {
queue_a_database_task(GC::create_function(realm.vm().heap(), [&realm, entry, db, &events_fired]() {
fire_a_version_change_event(realm, HTML::EventNames::versionchange, *entry, db->version(), {});
events_fired++;
}));
} else {
events_fired++;
}));
} else {
events_fired++;
}
}
// 7. Wait for all of the events to be fired.
HTML::main_thread_event_loop().spin_until(GC::create_function(realm.vm().heap(), [&events_to_fire, &events_fired]() {
if constexpr (IDB_DEBUG) {
dbgln("delete_a_database: waiting for step 7");
dbgln("events_fired: {}, events_to_fire: {}", events_fired, events_to_fire);
}
return events_fired == events_to_fire;
}));
// 8. If any of the connections in openConnections are still not closed, queue a database task to fire a version change event named blocked at request with dbs version and null.
for (auto const& entry : open_connections) {
if (entry->state() != IDBDatabase::ConnectionState::Closed) {
queue_a_database_task(GC::create_function(realm.vm().heap(), [&realm, entry, db]() {
fire_a_version_change_event(realm, HTML::EventNames::blocked, *entry, db->version(), {});
}));
}
}
// 9. Wait until all connections in openConnections are closed.
HTML::main_thread_event_loop().spin_until(GC::create_function(realm.vm().heap(), [open_connections]() {
if constexpr (IDB_DEBUG) {
dbgln("delete_a_database: waiting for step 9");
dbgln("open connections: {}", open_connections.size());
for (auto const& connection : open_connections) {
dbgln(" - {}", connection->uuid());
}
}
// 7. Wait for all of the events to be fired.
HTML::main_thread_event_loop().spin_until(GC::create_function(realm.vm().heap(), [&events_to_fire, &events_fired]() {
if constexpr (IDB_DEBUG) {
dbgln("delete_a_database: waiting for step 7");
dbgln("events_fired: {}, events_to_fire: {}", events_fired, events_to_fire);
}
return events_fired == events_to_fire;
}));
// 8. If any of the connections in openConnections are still not closed, queue a database task to fire a version change event named blocked at request with dbs version and null.
for (auto const& entry : open_connections) {
if (entry->state() != IDBDatabase::ConnectionState::Closed) {
return false;
queue_a_database_task(GC::create_function(realm.vm().heap(), [&realm, entry, db]() {
fire_a_version_change_event(realm, HTML::EventNames::blocked, *entry, db->version(), {});
}));
}
}
return true;
// 9. Wait until all connections in openConnections are closed.
HTML::main_thread_event_loop().spin_until(GC::create_function(realm.vm().heap(), [open_connections]() {
if constexpr (IDB_DEBUG) {
dbgln("delete_a_database: waiting for step 9");
dbgln("open connections: {}", open_connections.size());
for (auto const& connection : open_connections) {
dbgln(" - {}", connection->uuid());
}
}
for (auto const& entry : open_connections) {
if (entry->state() != IDBDatabase::ConnectionState::Closed) {
return false;
}
}
return true;
}));
// 10. Let version be dbs version.
auto version = db->version();
// 11. Delete db. If this fails for any reason, return an appropriate error (e.g. "QuotaExceededError" or "UnknownError" DOMException).
auto maybe_deleted = Database::delete_for_key_and_name(storage_key, name);
if (maybe_deleted.is_error()) {
on_complete->function()(WebIDL::OperationError::create(realm, "Unable to delete database"_utf16));
return;
}
// 12. Return version.
on_complete->function()(version);
}));
// 10. Let version be dbs version.
auto version = db->version();
// 11. Delete db. If this fails for any reason, return an appropriate error (e.g. "QuotaExceededError" or "UnknownError" DOMException).
auto maybe_deleted = Database::delete_for_key_and_name(storage_key, name);
if (maybe_deleted.is_error())
return WebIDL::OperationError::create(realm, "Unable to delete database"_utf16);
// 12. Return version.
return version;
}
// https://w3c.github.io/IndexedDB/#abort-a-transaction
@ -751,48 +756,44 @@ void commit_a_transaction(JS::Realm& realm, GC::Ref<IDBTransaction> transaction)
// 2. Run the following steps in parallel:
Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm.heap(), [&realm, transaction]() {
HTML::TemporaryExecutionContext context(realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes);
// 1. Wait until every item in transactions request list is processed.
HTML::main_thread_event_loop().spin_until(GC::create_function(realm.vm().heap(), [transaction]() {
if constexpr (IDB_DEBUG) {
dbgln("commit_a_transaction: waiting for step 1");
dbgln("requests in queue:");
for (auto const& request : transaction->request_list()) {
dbgln(" - {} = {}", request->uuid(), request->processed() ? "processed"sv : "not processed"sv);
if constexpr (IDB_DEBUG) {
dbgln("commit_a_transaction: waiting for step 1");
dbgln("requests in queue:");
for (auto const& request : transaction->request_list()) {
dbgln(" - {} = {}", request->uuid(), request->processed() ? "processed"sv : "not processed"sv);
}
}
transaction->request_list().all_requests_processed(realm.heap(), GC::create_function(realm.heap(), [transaction] {
// 2. If transactions state is no longer committing, then terminate these steps.
if (transaction->state() != IDBTransaction::TransactionState::Committing)
return;
// FIXME: 3. Attempt to write any outstanding changes made by transaction to the database, considering transactions durability hint.
// FIXME: 4. If an error occurs while writing the changes to the database, then run abort a transaction with transaction and an appropriate type for the error, for example "QuotaExceededError" or "UnknownError" DOMException, and terminate these steps.
// 5. Queue a database task to run these steps:
queue_a_database_task(GC::create_function(transaction->realm().vm().heap(), [transaction]() {
// 1. If transaction is an upgrade transaction, then set transactions connection's associated database's upgrade transaction to null.
if (transaction->is_upgrade_transaction())
transaction->connection()->associated_database()->set_upgrade_transaction(nullptr);
// 2. Set transactions state to finished.
transaction->set_state(IDBTransaction::TransactionState::Finished);
// 3. Fire an event named complete at transaction.
transaction->dispatch_event(DOM::Event::create(transaction->realm(), HTML::EventNames::complete));
// 4. If transaction is an upgrade transaction, then let request be the request associated with transaction and set requests transaction to null.
if (transaction->is_upgrade_transaction()) {
auto request = transaction->associated_request();
request->set_transaction(nullptr);
// Ad-hoc: Clear the two-way binding.
transaction->set_associated_request(nullptr);
}
}
return transaction->request_list().all_requests_processed();
}));
// 2. If transactions state is no longer committing, then terminate these steps.
if (transaction->state() != IDBTransaction::TransactionState::Committing)
return;
// FIXME: 3. Attempt to write any outstanding changes made by transaction to the database, considering transactions durability hint.
// FIXME: 4. If an error occurs while writing the changes to the database, then run abort a transaction with transaction and an appropriate type for the error, for example "QuotaExceededError" or "UnknownError" DOMException, and terminate these steps.
// 5. Queue a database task to run these steps:
queue_a_database_task(GC::create_function(transaction->realm().vm().heap(), [transaction]() {
// 1. If transaction is an upgrade transaction, then set transactions connection's associated database's upgrade transaction to null.
if (transaction->is_upgrade_transaction())
transaction->connection()->associated_database()->set_upgrade_transaction(nullptr);
// 2. Set transactions state to finished.
transaction->set_state(IDBTransaction::TransactionState::Finished);
// 3. Fire an event named complete at transaction.
transaction->dispatch_event(DOM::Event::create(transaction->realm(), HTML::EventNames::complete));
// 4. If transaction is an upgrade transaction, then let request be the request associated with transaction and set requests transaction to null.
if (transaction->is_upgrade_transaction()) {
auto request = transaction->associated_request();
request->set_transaction(nullptr);
// Ad-hoc: Clear the two-way binding.
transaction->set_associated_request(nullptr);
}
}));
}));
}));
}
@ -1158,63 +1159,59 @@ GC::Ref<IDBRequest> asynchronously_execute_a_request(JS::Realm& realm, IDBReques
// 5. Run these steps in parallel:
Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm.heap(), [&realm, transaction, operation, request]() {
HTML::TemporaryExecutionContext context(realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes);
// 1. Wait until request is the first item in transactions request list that is not processed.
HTML::main_thread_event_loop().spin_until(GC::create_function(realm.vm().heap(), [transaction, request]() {
if constexpr (IDB_DEBUG) {
dbgln("asynchronously_execute_a_request: waiting for step 5.1");
dbgln("requests in queue:");
for (auto const& item : transaction->request_list()) {
dbgln("[{}] - {} = {}", item == request ? "x"sv : " "sv, item->uuid(), item->processed() ? "processed"sv : "not processed"sv);
}
if constexpr (IDB_DEBUG) {
dbgln("asynchronously_execute_a_request: waiting for step 5.1");
dbgln("requests in queue:");
for (auto const& item : transaction->request_list()) {
dbgln("[{}] - {} = {}", item == request ? "x"sv : " "sv, item->uuid(), item->processed() ? "processed"sv : "not processed"sv);
}
return transaction->request_list().all_previous_requests_processed(request);
}));
// 2. Let result be the result of performing operation.
auto result = operation->function()();
// 3. If result is an error and transactions state is committing, then run abort a transaction with transaction and result, and terminate these steps.
if (result.is_error() && transaction->state() == IDBTransaction::TransactionState::Committing) {
abort_a_transaction(*transaction, result.exception().get<GC::Ref<WebIDL::DOMException>>());
return;
}
// FIXME: 4. If result is an error, then revert all changes made by operation.
transaction->request_list().all_previous_requests_processed(realm.heap(), request, GC::create_function(realm.heap(), [&realm, transaction, operation, request] {
// 2. Let result be the result of performing operation.
auto result = operation->function()();
// 5. Set requests processed flag to true.
request->set_processed(true);
// 6. Queue a database task to run these steps:
queue_a_database_task(GC::create_function(realm.vm().heap(), [&realm, request, result, transaction]() mutable {
// 1. Remove request from transactions request list.
transaction->request_list().remove_first_matching([&request](auto& entry) { return entry.ptr() == request.ptr(); });
// 2. Set requests done flag to true.
request->set_done(true);
// 3. If result is an error, then:
if (result.is_error()) {
// 1. Set requests result to undefined.
request->set_result(JS::js_undefined());
// 2. Set requests error to result.
request->set_error(result.exception().get<GC::Ref<WebIDL::DOMException>>());
// 3. Fire an error event at request.
fire_an_error_event(realm, request);
} else {
// 1. Set requests result to result.
request->set_result(result.release_value());
// 2. Set requests error to undefined.
request->set_error(Optional<GC::Ptr<WebIDL::DOMException>> {});
// 3. Fire a success event at request.
fire_a_success_event(realm, request);
// 3. If result is an error and transactions state is committing, then run abort a transaction with transaction and result, and terminate these steps.
if (result.is_error() && transaction->state() == IDBTransaction::TransactionState::Committing) {
abort_a_transaction(*transaction, result.exception().get<GC::Ref<WebIDL::DOMException>>());
return;
}
// FIXME: 4. If result is an error, then revert all changes made by operation.
// 5. Set requests processed flag to true.
request->set_processed(true);
// 6. Queue a database task to run these steps:
queue_a_database_task(GC::create_function(realm.vm().heap(), [&realm, request, result, transaction]() mutable {
// 1. Remove request from transactions request list.
transaction->request_list().remove_first_matching([&request](auto& entry) { return entry.ptr() == request.ptr(); });
// 2. Set requests done flag to true.
request->set_done(true);
// 3. If result is an error, then:
if (result.is_error()) {
// 1. Set requests result to undefined.
request->set_result(JS::js_undefined());
// 2. Set requests error to result.
request->set_error(result.exception().get<GC::Ref<WebIDL::DOMException>>());
// 3. Fire an error event at request.
fire_an_error_event(realm, request);
} else {
// 1. Set requests result to result.
request->set_result(result.release_value());
// 2. Set requests error to undefined.
request->set_error(Optional<GC::Ptr<WebIDL::DOMException>> {});
// 3. Fire a success event at request.
fire_a_success_event(realm, request);
}
}));
}));
}));