From 1057c88fddaa678d0538ba74cbad12554f129e1a Mon Sep 17 00:00:00 2001 From: stelar7 Date: Mon, 24 Mar 2025 20:43:12 +0100 Subject: [PATCH] LibWeb/IDB: Implement IDBDatabase::createObjectStore --- Libraries/LibWeb/IndexedDB/IDBDatabase.cpp | 51 ++++++++++++++++++- Libraries/LibWeb/IndexedDB/IDBDatabase.h | 13 ++++- Libraries/LibWeb/IndexedDB/IDBDatabase.idl | 3 +- Libraries/LibWeb/IndexedDB/IDBObjectStore.cpp | 17 +++++-- Libraries/LibWeb/IndexedDB/IDBObjectStore.h | 15 ++++-- .../LibWeb/IndexedDB/Internal/Database.cpp | 10 ++++ .../LibWeb/IndexedDB/Internal/Database.h | 3 ++ 7 files changed, 102 insertions(+), 10 deletions(-) diff --git a/Libraries/LibWeb/IndexedDB/IDBDatabase.cpp b/Libraries/LibWeb/IndexedDB/IDBDatabase.cpp index fa0c3487f90..899ba38c3c7 100644 --- a/Libraries/LibWeb/IndexedDB/IDBDatabase.cpp +++ b/Libraries/LibWeb/IndexedDB/IDBDatabase.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, stelar7 + * Copyright (c) 2024-2025, stelar7 * * SPDX-License-Identifier: BSD-2-Clause */ @@ -8,6 +8,7 @@ #include #include #include +#include #include namespace Web::IndexedDB { @@ -90,4 +91,52 @@ void IDBDatabase::close() close_a_database_connection(*this); } +// https://w3c.github.io/IndexedDB/#dom-idbdatabase-createobjectstore +WebIDL::ExceptionOr> IDBDatabase::create_object_store(String const& name, IDBObjectStoreParameters const& options) +{ + auto& realm = this->realm(); + + // 1. Let database be this's associated database. + auto database = associated_database(); + + // 2. Let transaction be database’s upgrade transaction if it is not null, or throw an "InvalidStateError" DOMException otherwise. + auto transaction = database->upgrade_transaction(); + if (!transaction) + return WebIDL::InvalidStateError::create(realm, "Upgrade transaction is null"_string); + + // 3. If transaction’s state is not active, then throw a "TransactionInactiveError" DOMException. + if (transaction->state() != IDBTransaction::TransactionState::Active) + return WebIDL::TransactionInactiveError::create(realm, "Transaction is not active"_string); + + // 4. Let keyPath be options’s keyPath member if it is not undefined or null, or null otherwise. + auto key_path = options.key_path; + + // 5. If keyPath is not null and is not a valid key path, throw a "SyntaxError" DOMException. + if (key_path.has_value() && !is_valid_key_path(key_path.value())) + return WebIDL::SyntaxError::create(realm, "Invalid key path"_string); + + // 6. If an object store named name already exists in database throw a "ConstraintError" DOMException. + if (database->has_object_store_named(name)) + return WebIDL::ConstraintError::create(realm, "Object store already exists"_string); + + // 7. Let autoIncrement be options’s autoIncrement member. + auto auto_increment = options.auto_increment; + + bool is_empty_key_path_or_sequence = key_path.has_value() && key_path.value().visit([](String const& value) -> bool { return value.is_empty(); }, [](Vector const&) -> bool { return true; }); + + // 8. If autoIncrement is true and keyPath is an empty string or any sequence (empty or otherwise), throw an "InvalidAccessError" DOMException. + if (auto_increment && is_empty_key_path_or_sequence) + return WebIDL::InvalidAccessError::create(realm, "Auto increment is true and key path is empty or sequence"_string); + + // 9. Let store be a new object store in database. + // Set the created object store's name to name. + // If autoIncrement is true, then the created object store uses a key generator. + // If keyPath is not null, set the created object store's key path to keyPath. + auto object_store = ObjectStore::create(realm, name, auto_increment, key_path); + database->add_object_store(object_store); + + // 10. Return a new object store handle associated with store and transaction. + return IDBObjectStore::create(realm, object_store, *transaction); +} + } diff --git a/Libraries/LibWeb/IndexedDB/IDBDatabase.h b/Libraries/LibWeb/IndexedDB/IDBDatabase.h index 8504f213eab..b76b18010b9 100644 --- a/Libraries/LibWeb/IndexedDB/IDBDatabase.h +++ b/Libraries/LibWeb/IndexedDB/IDBDatabase.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, stelar7 + * Copyright (c) 2024-2025, stelar7 * * SPDX-License-Identifier: BSD-2-Clause */ @@ -11,10 +11,19 @@ #include #include #include +#include #include namespace Web::IndexedDB { +using KeyPath = Variant>; + +// https://w3c.github.io/IndexedDB/#dictdef-idbobjectstoreparameters +struct IDBObjectStoreParameters { + Optional key_path; + bool auto_increment { false }; +}; + // FIXME: I'm not sure if this object should do double duty as both the connection and the interface // but the spec treats it as such...? // https://w3c.github.io/IndexedDB/#IDBDatabase-interface @@ -44,6 +53,8 @@ public: [[nodiscard]] ConnectionState state() const { return m_state; } [[nodiscard]] GC::Ref associated_database() { return m_associated_database; } + WebIDL::ExceptionOr> create_object_store(String const&, IDBObjectStoreParameters const&); + void close(); void set_onabort(WebIDL::CallbackType*); diff --git a/Libraries/LibWeb/IndexedDB/IDBDatabase.idl b/Libraries/LibWeb/IndexedDB/IDBDatabase.idl index 917441e482f..d08d74008fb 100644 --- a/Libraries/LibWeb/IndexedDB/IDBDatabase.idl +++ b/Libraries/LibWeb/IndexedDB/IDBDatabase.idl @@ -1,5 +1,6 @@ #import #import +#import [Exposed=(Window,Worker)] interface IDBDatabase : EventTarget { @@ -9,7 +10,7 @@ interface IDBDatabase : EventTarget { [FIXME, NewObject] IDBTransaction transaction((DOMString or sequence) storeNames, optional IDBTransactionMode mode = "readonly", optional IDBTransactionOptions options = {}); undefined close(); - [FIXME, NewObject] IDBObjectStore createObjectStore(DOMString name, optional IDBObjectStoreParameters options = {}); + [NewObject] IDBObjectStore createObjectStore(DOMString name, optional IDBObjectStoreParameters options = {}); [FIXME] undefined deleteObjectStore(DOMString name); // Event handlers: diff --git a/Libraries/LibWeb/IndexedDB/IDBObjectStore.cpp b/Libraries/LibWeb/IndexedDB/IDBObjectStore.cpp index c93373cec2b..a384ac28932 100644 --- a/Libraries/LibWeb/IndexedDB/IDBObjectStore.cpp +++ b/Libraries/LibWeb/IndexedDB/IDBObjectStore.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, stelar7 + * Copyright (c) 2024-2025, stelar7 * * SPDX-License-Identifier: BSD-2-Clause */ @@ -15,14 +15,16 @@ GC_DEFINE_ALLOCATOR(IDBObjectStore); IDBObjectStore::~IDBObjectStore() = default; -IDBObjectStore::IDBObjectStore(JS::Realm& realm) +IDBObjectStore::IDBObjectStore(JS::Realm& realm, GC::Ref store, GC::Ref transaction) : PlatformObject(realm) + , m_store(store) + , m_transaction(transaction) { } -GC::Ref IDBObjectStore::create(JS::Realm& realm) +GC::Ref IDBObjectStore::create(JS::Realm& realm, GC::Ref store, GC::Ref transaction) { - return realm.create(realm); + return realm.create(realm, store, transaction); } void IDBObjectStore::initialize(JS::Realm& realm) @@ -31,4 +33,11 @@ void IDBObjectStore::initialize(JS::Realm& realm) WEB_SET_PROTOTYPE_FOR_INTERFACE(IDBObjectStore); } +void IDBObjectStore::visit_edges(Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_store); + visitor.visit(m_transaction); +} + } diff --git a/Libraries/LibWeb/IndexedDB/IDBObjectStore.h b/Libraries/LibWeb/IndexedDB/IDBObjectStore.h index 08cd335c557..854845e05e7 100644 --- a/Libraries/LibWeb/IndexedDB/IDBObjectStore.h +++ b/Libraries/LibWeb/IndexedDB/IDBObjectStore.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, stelar7 + * Copyright (c) 2024-2025, stelar7 * * SPDX-License-Identifier: BSD-2-Clause */ @@ -8,21 +8,30 @@ #include #include +#include +#include namespace Web::IndexedDB { // https://w3c.github.io/IndexedDB/#object-store-interface +// https://w3c.github.io/IndexedDB/#object-store-handle-construct class IDBObjectStore : public Bindings::PlatformObject { WEB_PLATFORM_OBJECT(IDBObjectStore, Bindings::PlatformObject); GC_DECLARE_ALLOCATOR(IDBObjectStore); public: virtual ~IDBObjectStore() override; - [[nodiscard]] static GC::Ref create(JS::Realm&); + [[nodiscard]] static GC::Ref create(JS::Realm&, GC::Ref, GC::Ref); protected: - explicit IDBObjectStore(JS::Realm&); + explicit IDBObjectStore(JS::Realm&, GC::Ref, GC::Ref); virtual void initialize(JS::Realm&) override; + virtual void visit_edges(Visitor& visitor) override; + +private: + // An object store handle has an associated object store and an associated transaction. + GC::Ref m_store; + GC::Ref m_transaction; }; } diff --git a/Libraries/LibWeb/IndexedDB/Internal/Database.cpp b/Libraries/LibWeb/IndexedDB/Internal/Database.cpp index 174ef6bafc8..96f414763bd 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Database.cpp +++ b/Libraries/LibWeb/IndexedDB/Internal/Database.cpp @@ -29,6 +29,16 @@ void Database::visit_edges(Visitor& visitor) visitor.visit(m_object_stores); } +bool Database::has_object_store_named(String const& name) const +{ + for (auto const& object_store : m_object_stores) { + if (object_store->name() == name) + return true; + } + + return false; +} + Vector> Database::for_key(StorageAPI::StorageKey const& key) { Vector> databases; diff --git a/Libraries/LibWeb/IndexedDB/Internal/Database.h b/Libraries/LibWeb/IndexedDB/Internal/Database.h index 3dd705ee6be..c5ac1d03eb6 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Database.h +++ b/Libraries/LibWeb/IndexedDB/Internal/Database.h @@ -40,6 +40,9 @@ public: return connections; } + bool has_object_store_named(String const& name) const; + void add_object_store(GC::Ref object_store) { m_object_stores.append(object_store); } + [[nodiscard]] static Vector> for_key(StorageAPI::StorageKey const&); [[nodiscard]] static Optional const&> for_key_and_name(StorageAPI::StorageKey&, String&); [[nodiscard]] static ErrorOr> create_for_key_and_name(JS::Realm&, StorageAPI::StorageKey&, String&);