/* * Copyright (c) 2024-2025, stelar7 * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include namespace Web::IndexedDB { GC_DEFINE_ALLOCATOR(IDBTransaction); IDBTransaction::~IDBTransaction() = default; IDBTransaction::IDBTransaction(JS::Realm& realm, GC::Ref connection, Bindings::IDBTransactionMode mode, Bindings::IDBTransactionDurability durability, Vector> scopes) : EventTarget(realm) , m_connection(connection) , m_mode(mode) , m_durability(durability) , m_scope(move(scopes)) { m_uuid = MUST(Crypto::generate_random_uuid()); connection->add_transaction(*this); } GC::Ref IDBTransaction::create(JS::Realm& realm, GC::Ref connection, Bindings::IDBTransactionMode mode, Bindings::IDBTransactionDurability durability = Bindings::IDBTransactionDurability::Default, Vector> scopes = {}) { return realm.create(realm, connection, mode, durability, move(scopes)); } void IDBTransaction::initialize(JS::Realm& realm) { WEB_SET_PROTOTYPE_FOR_INTERFACE(IDBTransaction); Base::initialize(realm); } void IDBTransaction::visit_edges(Visitor& visitor) { Base::visit_edges(visitor); visitor.visit(m_connection); visitor.visit(m_error); visitor.visit(m_associated_request); visitor.visit(m_scope); visitor.visit(m_cleanup_event_loop); for (auto& [store, handle] : m_object_store_handles) visitor.visit(handle); visitor.visit(m_index_handles); for (auto& entry : m_store_mutation_logs) { visitor.visit(entry.store); visitor.visit(entry.log); } } void IDBTransaction::set_onabort(WebIDL::CallbackType* event_handler) { set_event_handler_attribute(HTML::EventNames::abort, event_handler); } WebIDL::CallbackType* IDBTransaction::onabort() { return event_handler_attribute(HTML::EventNames::abort); } void IDBTransaction::set_oncomplete(WebIDL::CallbackType* event_handler) { set_event_handler_attribute(HTML::EventNames::complete, event_handler); } WebIDL::CallbackType* IDBTransaction::oncomplete() { return event_handler_attribute(HTML::EventNames::complete); } void IDBTransaction::set_onerror(WebIDL::CallbackType* event_handler) { set_event_handler_attribute(HTML::EventNames::error, event_handler); } WebIDL::CallbackType* IDBTransaction::onerror() { return event_handler_attribute(HTML::EventNames::error); } // https://w3c.github.io/IndexedDB/#dom-idbtransaction-abort WebIDL::ExceptionOr IDBTransaction::abort() { // 1. If this's state is committing or finished, then throw an "InvalidStateError" DOMException. if (m_state == TransactionState::Committing || m_state == TransactionState::Finished) return WebIDL::InvalidStateError::create(realm(), "Transaction is ending"_utf16); // 2. Run abort a transaction with this and null. abort_a_transaction(*this, nullptr); return {}; } // https://w3c.github.io/IndexedDB/#dom-idbtransaction-objectstorenames GC::Ref IDBTransaction::object_store_names() { // 1. Let names be a list of the names of the object stores in this's scope. Vector names; for (auto const& object_store : this->scope()) names.append(object_store->name()); // 2. Return the result (a DOMStringList) of creating a sorted name list with 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 committing"_utf16); // 2. Run commit a transaction with this. commit_a_transaction(realm, *this); return {}; } GC::Ptr IDBTransaction::object_store_named(String const& name) const { for (auto const& store : m_scope) { if (store->name() == name) return store; } return nullptr; } // https://w3c.github.io/IndexedDB/#dom-idbtransaction-objectstore WebIDL::ExceptionOr> IDBTransaction::object_store(String const& name) { auto& realm = this->realm(); // 1. If this's state is finished, then throw an "InvalidStateError" DOMException. if (m_state == TransactionState::Finished) return WebIDL::InvalidStateError::create(realm, "Transaction is finished"_utf16); // 2. Let store be the object store named name in this's scope, or throw a "NotFoundError" DOMException if none. auto store = object_store_named(name); if (!store) return WebIDL::NotFoundError::create(realm, "Object store not found in transactions scope"_utf16); // 3. Return an object store handle associated with store and this. // https://w3c.github.io/IndexedDB/#object-store-handle-construct // Multiple handles may be associated with the same object store in different transactions, // but there must be only one object store handle associated with a particular object store // within a transaction. return get_or_create_object_store_handle(*store); } GC::Ref IDBTransaction::get_or_create_object_store_handle(GC::Ref store) { // NOTE: We have to do two lookups here. If we use ensure() with a constructor callback, the garbage collector // may run when we construct the handle, and then we visit the HashMap in an invalid state. if (auto handle = m_object_store_handles.get(store); handle.has_value()) return handle.value(); auto handle = IDBObjectStore::create(realm(), store, *this); m_object_store_handles.set(store, handle); return handle; } GC::Ptr IDBTransaction::object_store_handle_for(GC::Ref store) { auto maybe_handle = m_object_store_handles.find(store); if (maybe_handle == m_object_store_handles.end()) return nullptr; return maybe_handle->value; } void IDBTransaction::set_state(TransactionState state) { m_state = state; } void IDBTransaction::register_index_handle(Badge, GC::Ref handle) { m_index_handles.append(handle); } void IDBTransaction::set_up_mutation_logs() { m_original_version = m_connection->associated_database()->version(); for (auto& store : m_scope) { auto log = MutationLog::create(realm()); store->set_mutation_log(log); m_store_mutation_logs.append({ store, log }); } } void IDBTransaction::set_up_mutation_log_for_new_store(GC::Ref store) { auto log = MutationLog::create(realm()); store->set_mutation_log(log); m_store_mutation_logs.append({ store, log }); log->note_object_store_created(); } void IDBTransaction::revert_all_mutations() { auto database = m_connection->associated_database(); for (size_t i = m_store_mutation_logs.size(); i > 0; --i) { auto& entry = m_store_mutation_logs[i - 1]; entry.log->revert(*entry.store, database, m_connection); } database->set_version(m_original_version); m_connection->set_version(m_original_version); } void IDBTransaction::discard_mutation_logs() { for (auto& entry : m_store_mutation_logs) entry.store->set_mutation_log(nullptr); m_store_mutation_logs.clear(); } }