diff --git a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp index 359b5d593a6..8033a803db9 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp +++ b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp @@ -19,7 +19,10 @@ #include #include #include +#include #include +#include +#include #include #include #include @@ -1108,4 +1111,101 @@ void fire_a_success_event(JS::Realm& realm, GC::Ref request) } } +// https://w3c.github.io/IndexedDB/#asynchronously-execute-a-request +GC::Ref asynchronously_execute_a_request(JS::Realm& realm, IDBRequestSource source, GC::Ref()>> operation, GC::Ptr request_input) +{ + // 1. Let transaction be the transaction associated with source. + auto transaction = source.visit( + [](Empty) -> GC::Ptr { + VERIFY_NOT_REACHED(); + }, + [](GC::Ref object_store) -> GC::Ptr { + return object_store->transaction(); + }, + [](GC::Ref index) -> GC::Ptr { + return index->transaction(); + }, + [](GC::Ref cursor) -> GC::Ptr { + return cursor->transaction(); + }); + + // 2. Assert: transaction’s state is active. + VERIFY(transaction->state() == IDBTransaction::TransactionState::Active); + + // 3. If request was not given, let request be a new request with source as source. + GC::Ref request = request_input ? GC::Ref(*request_input) : IDBRequest::create(realm, source); + + // 4. Add request to the end of transaction’s request list. + transaction->request_list().append(request); + + // Set the two-way binding. (Missing spec step) + // FIXME: https://github.com/w3c/IndexedDB/issues/433 + request->set_transaction(transaction); + + // 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 transaction’s 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); + } + } + + 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 transaction’s 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>()); + return; + } + + // FIXME: 4. If result is an error, then revert all changes made by operation. + + // 5. Set request’s processed flag to true. + request->set_processed(true); + + // 6. Queue a task to run these steps: + HTML::queue_a_task(HTML::Task::Source::DatabaseAccess, nullptr, nullptr, GC::create_function(realm.vm().heap(), [&realm, request, result, transaction]() mutable { + // 1. Remove request from transaction’s request list. + transaction->request_list().remove_first_matching([&request](auto& entry) { return entry.ptr() == request.ptr(); }); + + // 2. Set request’s done flag to true. + request->set_done(true); + + // 3. If result is an error, then: + if (result.is_error()) { + // 1. Set request’s result to undefined. + request->set_result(JS::js_undefined()); + + // 2. Set request’s error to result. + request->set_error(result.exception().get>()); + + // 3. Fire an error event at request. + fire_an_error_event(realm, request); + } else { + // 1. Set request’s result to result. + request->set_result(result.release_value()); + + // 2. Set request’s error to undefined. + request->set_error(nullptr); + + // 3. Fire a success event at request. + fire_a_success_event(realm, request); + } + })); + })); + + // 6. Return request. + return request; +} + } diff --git a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h index bcb8de51984..c1e01aabed4 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h +++ b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h @@ -35,5 +35,6 @@ WebIDL::ExceptionOr>> extract_a_key_from_a_value_using_a_ke bool check_that_a_key_could_be_injected_into_a_value(JS::Realm&, JS::Value, KeyPath const&); void fire_an_error_event(JS::Realm&, GC::Ref); void fire_a_success_event(JS::Realm&, GC::Ref); +GC::Ref asynchronously_execute_a_request(JS::Realm&, IDBRequestSource, GC::Ref()>>, GC::Ptr = nullptr); }