LibWeb/IDB: Implement IDBDatabase::createObjectStore

This commit is contained in:
stelar7 2025-03-24 20:43:12 +01:00 committed by Jelle Raaijmakers
parent 3c5578cc87
commit 1057c88fdd
Notes: github-actions[bot] 2025-03-27 15:49:41 +00:00
7 changed files with 102 additions and 10 deletions

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2024, stelar7 <dudedbz@gmail.com> * Copyright (c) 2024-2025, stelar7 <dudedbz@gmail.com>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -8,6 +8,7 @@
#include <LibWeb/Bindings/Intrinsics.h> #include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/HTML/EventNames.h> #include <LibWeb/HTML/EventNames.h>
#include <LibWeb/IndexedDB/IDBDatabase.h> #include <LibWeb/IndexedDB/IDBDatabase.h>
#include <LibWeb/IndexedDB/IDBObjectStore.h>
#include <LibWeb/IndexedDB/Internal/Algorithms.h> #include <LibWeb/IndexedDB/Internal/Algorithms.h>
namespace Web::IndexedDB { namespace Web::IndexedDB {
@ -90,4 +91,52 @@ void IDBDatabase::close()
close_a_database_connection(*this); close_a_database_connection(*this);
} }
// https://w3c.github.io/IndexedDB/#dom-idbdatabase-createobjectstore
WebIDL::ExceptionOr<GC::Ref<IDBObjectStore>> 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 databases 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 transactions 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 optionss 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 optionss 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<String> 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);
}
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2024, stelar7 <dudedbz@gmail.com> * Copyright (c) 2024-2025, stelar7 <dudedbz@gmail.com>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -11,10 +11,19 @@
#include <LibWeb/HTML/DOMStringList.h> #include <LibWeb/HTML/DOMStringList.h>
#include <LibWeb/IndexedDB/IDBRequest.h> #include <LibWeb/IndexedDB/IDBRequest.h>
#include <LibWeb/IndexedDB/Internal/Database.h> #include <LibWeb/IndexedDB/Internal/Database.h>
#include <LibWeb/IndexedDB/Internal/ObjectStore.h>
#include <LibWeb/StorageAPI/StorageKey.h> #include <LibWeb/StorageAPI/StorageKey.h>
namespace Web::IndexedDB { namespace Web::IndexedDB {
using KeyPath = Variant<String, Vector<String>>;
// https://w3c.github.io/IndexedDB/#dictdef-idbobjectstoreparameters
struct IDBObjectStoreParameters {
Optional<KeyPath> 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 // 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...? // but the spec treats it as such...?
// https://w3c.github.io/IndexedDB/#IDBDatabase-interface // https://w3c.github.io/IndexedDB/#IDBDatabase-interface
@ -44,6 +53,8 @@ public:
[[nodiscard]] ConnectionState state() const { return m_state; } [[nodiscard]] ConnectionState state() const { return m_state; }
[[nodiscard]] GC::Ref<Database> associated_database() { return m_associated_database; } [[nodiscard]] GC::Ref<Database> associated_database() { return m_associated_database; }
WebIDL::ExceptionOr<GC::Ref<IDBObjectStore>> create_object_store(String const&, IDBObjectStoreParameters const&);
void close(); void close();
void set_onabort(WebIDL::CallbackType*); void set_onabort(WebIDL::CallbackType*);

View file

@ -1,5 +1,6 @@
#import <DOM/EventTarget.idl> #import <DOM/EventTarget.idl>
#import <DOM/EventHandler.idl> #import <DOM/EventHandler.idl>
#import <IndexedDB/IDBObjectStore.idl>
[Exposed=(Window,Worker)] [Exposed=(Window,Worker)]
interface IDBDatabase : EventTarget { interface IDBDatabase : EventTarget {
@ -9,7 +10,7 @@ interface IDBDatabase : EventTarget {
[FIXME, NewObject] IDBTransaction transaction((DOMString or sequence<DOMString>) storeNames, optional IDBTransactionMode mode = "readonly", optional IDBTransactionOptions options = {}); [FIXME, NewObject] IDBTransaction transaction((DOMString or sequence<DOMString>) storeNames, optional IDBTransactionMode mode = "readonly", optional IDBTransactionOptions options = {});
undefined close(); undefined close();
[FIXME, NewObject] IDBObjectStore createObjectStore(DOMString name, optional IDBObjectStoreParameters options = {}); [NewObject] IDBObjectStore createObjectStore(DOMString name, optional IDBObjectStoreParameters options = {});
[FIXME] undefined deleteObjectStore(DOMString name); [FIXME] undefined deleteObjectStore(DOMString name);
// Event handlers: // Event handlers:

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2024, stelar7 <dudedbz@gmail.com> * Copyright (c) 2024-2025, stelar7 <dudedbz@gmail.com>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -15,14 +15,16 @@ GC_DEFINE_ALLOCATOR(IDBObjectStore);
IDBObjectStore::~IDBObjectStore() = default; IDBObjectStore::~IDBObjectStore() = default;
IDBObjectStore::IDBObjectStore(JS::Realm& realm) IDBObjectStore::IDBObjectStore(JS::Realm& realm, GC::Ref<ObjectStore> store, GC::Ref<IDBTransaction> transaction)
: PlatformObject(realm) : PlatformObject(realm)
, m_store(store)
, m_transaction(transaction)
{ {
} }
GC::Ref<IDBObjectStore> IDBObjectStore::create(JS::Realm& realm) GC::Ref<IDBObjectStore> IDBObjectStore::create(JS::Realm& realm, GC::Ref<ObjectStore> store, GC::Ref<IDBTransaction> transaction)
{ {
return realm.create<IDBObjectStore>(realm); return realm.create<IDBObjectStore>(realm, store, transaction);
} }
void IDBObjectStore::initialize(JS::Realm& realm) void IDBObjectStore::initialize(JS::Realm& realm)
@ -31,4 +33,11 @@ void IDBObjectStore::initialize(JS::Realm& realm)
WEB_SET_PROTOTYPE_FOR_INTERFACE(IDBObjectStore); WEB_SET_PROTOTYPE_FOR_INTERFACE(IDBObjectStore);
} }
void IDBObjectStore::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_store);
visitor.visit(m_transaction);
}
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2024, stelar7 <dudedbz@gmail.com> * Copyright (c) 2024-2025, stelar7 <dudedbz@gmail.com>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -8,21 +8,30 @@
#include <LibGC/Heap.h> #include <LibGC/Heap.h>
#include <LibWeb/Bindings/PlatformObject.h> #include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/IndexedDB/IDBTransaction.h>
#include <LibWeb/IndexedDB/Internal/ObjectStore.h>
namespace Web::IndexedDB { namespace Web::IndexedDB {
// https://w3c.github.io/IndexedDB/#object-store-interface // https://w3c.github.io/IndexedDB/#object-store-interface
// https://w3c.github.io/IndexedDB/#object-store-handle-construct
class IDBObjectStore : public Bindings::PlatformObject { class IDBObjectStore : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(IDBObjectStore, Bindings::PlatformObject); WEB_PLATFORM_OBJECT(IDBObjectStore, Bindings::PlatformObject);
GC_DECLARE_ALLOCATOR(IDBObjectStore); GC_DECLARE_ALLOCATOR(IDBObjectStore);
public: public:
virtual ~IDBObjectStore() override; virtual ~IDBObjectStore() override;
[[nodiscard]] static GC::Ref<IDBObjectStore> create(JS::Realm&); [[nodiscard]] static GC::Ref<IDBObjectStore> create(JS::Realm&, GC::Ref<ObjectStore>, GC::Ref<IDBTransaction>);
protected: protected:
explicit IDBObjectStore(JS::Realm&); explicit IDBObjectStore(JS::Realm&, GC::Ref<ObjectStore>, GC::Ref<IDBTransaction>);
virtual void initialize(JS::Realm&) override; 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<ObjectStore> m_store;
GC::Ref<IDBTransaction> m_transaction;
}; };
} }

View file

@ -29,6 +29,16 @@ void Database::visit_edges(Visitor& visitor)
visitor.visit(m_object_stores); 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<GC::Root<Database>> Database::for_key(StorageAPI::StorageKey const& key) Vector<GC::Root<Database>> Database::for_key(StorageAPI::StorageKey const& key)
{ {
Vector<GC::Root<Database>> databases; Vector<GC::Root<Database>> databases;

View file

@ -40,6 +40,9 @@ public:
return connections; return connections;
} }
bool has_object_store_named(String const& name) const;
void add_object_store(GC::Ref<ObjectStore> object_store) { m_object_stores.append(object_store); }
[[nodiscard]] static Vector<GC::Root<Database>> for_key(StorageAPI::StorageKey const&); [[nodiscard]] static Vector<GC::Root<Database>> for_key(StorageAPI::StorageKey const&);
[[nodiscard]] static Optional<GC::Root<Database> const&> for_key_and_name(StorageAPI::StorageKey&, String&); [[nodiscard]] static Optional<GC::Root<Database> const&> for_key_and_name(StorageAPI::StorageKey&, String&);
[[nodiscard]] static ErrorOr<GC::Root<Database>> create_for_key_and_name(JS::Realm&, StorageAPI::StorageKey&, String&); [[nodiscard]] static ErrorOr<GC::Root<Database>> create_for_key_and_name(JS::Realm&, StorageAPI::StorageKey&, String&);