diff --git a/Libraries/LibWeb/IndexedDB/IDBTransaction.cpp b/Libraries/LibWeb/IndexedDB/IDBTransaction.cpp index 7cca446090f..6e58b3ba56c 100644 --- a/Libraries/LibWeb/IndexedDB/IDBTransaction.cpp +++ b/Libraries/LibWeb/IndexedDB/IDBTransaction.cpp @@ -103,4 +103,19 @@ GC::Ref IDBTransaction::object_store_names() return create_a_sorted_name_list(realm(), names); } +// https://w3c.github.io/IndexedDB/#dom-idbtransaction-commit +WebIDL::ExceptionOr IDBTransaction::commit() +{ + auto& realm = this->realm(); + + // 1. If this's state is not active, then throw an "InvalidStateError" DOMException. + if (m_state != TransactionState::Active) + return WebIDL::InvalidStateError::create(realm, "Transaction is not active while commiting"_string); + + // 2. Run commit a transaction with this. + commit_a_transaction(realm, *this); + + return {}; +} + } diff --git a/Libraries/LibWeb/IndexedDB/IDBTransaction.h b/Libraries/LibWeb/IndexedDB/IDBTransaction.h index 194d40c7023..bc5af18a485 100644 --- a/Libraries/LibWeb/IndexedDB/IDBTransaction.h +++ b/Libraries/LibWeb/IndexedDB/IDBTransaction.h @@ -44,6 +44,7 @@ public: [[nodiscard]] GC::Ptr associated_request() const { return m_associated_request; } [[nodiscard]] bool aborted() const { return m_aborted; } [[nodiscard]] GC::Ref object_store_names(); + [[nodiscard]] RequestList& request_list() { return m_request_list; } [[nodiscard]] ReadonlySpan> scope() const { return m_scope; } [[nodiscard]] String uuid() const { return m_uuid; } @@ -59,6 +60,7 @@ public: [[nodiscard]] bool is_finished() const { return m_state == TransactionState::Finished; } WebIDL::ExceptionOr abort(); + WebIDL::ExceptionOr commit(); void set_onabort(WebIDL::CallbackType*); WebIDL::CallbackType* onabort(); diff --git a/Libraries/LibWeb/IndexedDB/IDBTransaction.idl b/Libraries/LibWeb/IndexedDB/IDBTransaction.idl index c343a328ab3..667d36a6cdd 100644 --- a/Libraries/LibWeb/IndexedDB/IDBTransaction.idl +++ b/Libraries/LibWeb/IndexedDB/IDBTransaction.idl @@ -10,7 +10,7 @@ interface IDBTransaction : EventTarget { [SameObject, ImplementedAs=connection] readonly attribute IDBDatabase db; readonly attribute DOMException? error; [FIXME] IDBObjectStore objectStore(DOMString name); - [FIXME] undefined commit(); + undefined commit(); undefined abort(); attribute EventHandler onabort; diff --git a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp index abfe6d31b8d..4b325a9fab8 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp +++ b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -706,4 +708,60 @@ GC::Ref create_a_sorted_name_list(JS::Realm& realm, Vector< return HTML::DOMStringList::create(realm, names); } +// https://w3c.github.io/IndexedDB/#commit-a-transaction +void commit_a_transaction(JS::Realm& realm, GC::Ref transaction) +{ + // 1. Set transaction’s state to committing. + transaction->set_state(IDBTransaction::TransactionState::Committing); + + dbgln_if(IDB_DEBUG, "commit_a_transaction: transaction {} is committing", transaction->uuid()); + + // 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 transaction’s 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); + } + } + + return transaction->request_list().all_requests_processed(); + })); + + // 2. If transaction’s 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 transaction’s 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 task to run these steps: + HTML::queue_a_task(HTML::Task::Source::DatabaseAccess, nullptr, nullptr, GC::create_function(transaction->realm().vm().heap(), [transaction]() { + // 1. If transaction is an upgrade transaction, then set transaction’s connection's associated database's upgrade transaction to null. + if (transaction->is_upgrade_transaction()) + transaction->connection()->associated_database()->set_upgrade_transaction(nullptr); + + // 2. Set transaction’s 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 request’s 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); + } + })); + })); +} + } diff --git a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h index 09a33be3f48..7d7540e5fe7 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h +++ b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h @@ -27,5 +27,6 @@ void abort_a_transaction(GC::Ref, GC::Ptr) JS::Value convert_a_key_to_a_value(JS::Realm&, GC::Ref); bool is_valid_key_path(KeyPath const&); GC::Ref create_a_sorted_name_list(JS::Realm&, Vector); +void commit_a_transaction(JS::Realm&, GC::Ref); }