Files
ladybird/Libraries/LibWeb/IndexedDB/IDBDatabase.h
Zaggy1024 61b9be47ce LibWeb: Open waiting IDB connections when the previous one is GCed
Without this, an open request could hang if a prior connection was not
explicitly close()d but instead allowed to go out of scope.

The spec says that the dangling connection should be closed when the
execution context it was opened in is destroyed, but no other browser
does so. Detecting the execution context being destroyed would likely
mean a lot of overhead in JS calls, so it's best to avoid that, despite
this being observable.
2026-04-08 03:03:38 +02:00

130 lines
5.1 KiB
C++

/*
* Copyright (c) 2024-2025, stelar7 <dudedbz@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibGC/Ptr.h>
#include <LibWeb/Bindings/IDBDatabasePrototype.h>
#include <LibWeb/DOM/EventTarget.h>
#include <LibWeb/HTML/DOMStringList.h>
#include <LibWeb/IndexedDB/ConnectionState.h>
#include <LibWeb/IndexedDB/IDBRequest.h>
#include <LibWeb/IndexedDB/IDBTransaction.h>
#include <LibWeb/IndexedDB/Internal/Database.h>
#include <LibWeb/IndexedDB/Internal/ObjectStore.h>
#include <LibWeb/StorageAPI/StorageKey.h>
namespace Web::IndexedDB {
using KeyPath = Variant<String, Vector<String>>;
using NullableKeyPath = Variant<String, Vector<String>, Empty>;
// https://w3c.github.io/IndexedDB/#dictdef-idbobjectstoreparameters
struct IDBObjectStoreParameters {
NullableKeyPath key_path { Empty {} };
bool auto_increment { false };
};
// https://w3c.github.io/IndexedDB/#dictdef-idbtransactionoptions
struct IDBTransactionOptions {
Bindings::IDBTransactionDurability durability = Bindings::IDBTransactionDurability::Default;
};
// https://w3c.github.io/IndexedDB/#IDBDatabase-interface
// https://www.w3.org/TR/IndexedDB/#database-connection
class IDBDatabase : public DOM::EventTarget {
WEB_PLATFORM_OBJECT(IDBDatabase, DOM::EventTarget);
GC_DECLARE_ALLOCATOR(IDBDatabase);
public:
static constexpr bool OVERRIDES_FINALIZE = true;
virtual ~IDBDatabase() override;
virtual void finalize() override;
[[nodiscard]] static GC::Ref<IDBDatabase> create(JS::Realm&, Database&);
void set_version(u64 version) { m_version = version; }
void set_close_pending(bool close_pending) { m_close_pending = close_pending; }
void set_state(ConnectionState state);
[[nodiscard]] String uuid() const { return m_uuid; }
[[nodiscard]] String name() const { return m_name; }
[[nodiscard]] u64 version() const { return m_version; }
[[nodiscard]] bool close_pending() const { return m_close_pending; }
[[nodiscard]] ConnectionState state() const { return m_state; }
[[nodiscard]] GC::Ref<Database> associated_database() { return m_associated_database; }
[[nodiscard]] ReadonlySpan<GC::Ref<ObjectStore>> object_store_set() { return m_object_store_set; }
void add_to_object_store_set(GC::Ref<ObjectStore> object_store) { m_object_store_set.append(object_store); }
void remove_from_object_store_set(GC::Ref<ObjectStore> object_store)
{
m_object_store_set.remove_first_matching([&](auto& entry) { return entry == object_store; });
}
[[nodiscard]] ReadonlySpan<GC::Ref<IDBTransaction>> transactions() { return m_transactions; }
void add_transaction(GC::Ref<IDBTransaction> transaction) { m_transactions.append(transaction); }
[[nodiscard]] GC::Ref<HTML::DOMStringList> object_store_names();
WebIDL::ExceptionOr<GC::Ref<IDBObjectStore>> create_object_store(String const&, IDBObjectStoreParameters const&);
WebIDL::ExceptionOr<void> delete_object_store(String const&);
WebIDL::ExceptionOr<GC::Ref<IDBTransaction>> transaction(Variant<String, Vector<String>>, Bindings::IDBTransactionMode = Bindings::IDBTransactionMode::Readonly, IDBTransactionOptions = { .durability = Bindings::IDBTransactionDurability::Default });
void close();
void set_onabort(WebIDL::CallbackType*);
WebIDL::CallbackType* onabort();
void set_onclose(WebIDL::CallbackType*);
WebIDL::CallbackType* onclose();
void set_onerror(WebIDL::CallbackType*);
WebIDL::CallbackType* onerror();
void set_onversionchange(WebIDL::CallbackType*);
WebIDL::CallbackType* onversionchange();
void wait_for_transactions_to_finish(ReadonlySpan<GC::Ref<IDBTransaction>>, GC::Ref<GC::Function<void()>> on_complete);
void check_pending_transaction_waits();
void block_on_conflicting_transactions(GC::Ref<IDBTransaction>);
protected:
explicit IDBDatabase(JS::Realm&, Database&);
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Visitor& visitor) override;
private:
struct PendingTransactionWait {
Vector<GC::Ref<IDBTransaction>> transactions;
GC::Ref<GC::Function<void()>> callback;
};
Vector<PendingTransactionWait> m_pending_transaction_waits;
u64 m_version { 0 };
String m_name;
// Each connection has a close pending flag which is initially false.
bool m_close_pending { false };
// When a connection is initially created it is in an opened state.
ConnectionState m_state { ConnectionState::Open };
// A connection has an object store set, which is initialized to the set of object stores in the associated database when the connection is created.
// The contents of the set will remain constant except when an upgrade transaction is live.
Vector<GC::Ref<ObjectStore>> m_object_store_set;
// NOTE: There is an associated database in the spec, but there is no mention where it is assigned, nor where its from
// So we stash the one we have when opening a connection.
GC::Ref<Database> m_associated_database;
// NOTE: We need to keep track of what transactions were created by this connection
Vector<GC::Ref<IDBTransaction>> m_transactions;
// NOTE: Used for debug purposes
String m_uuid;
};
}