2024-11-04 18:53:05 +01:00
/*
2025-03-24 20:43:12 +01:00
* Copyright ( c ) 2024 - 2025 , stelar7 < dudedbz @ gmail . com >
2024-11-04 18:53:05 +01:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include <LibWeb/Bindings/IDBDatabasePrototype.h>
# include <LibWeb/Bindings/Intrinsics.h>
2025-04-02 10:28:04 +02:00
# include <LibWeb/Crypto/Crypto.h>
2024-11-04 18:53:05 +01:00
# include <LibWeb/HTML/EventNames.h>
# include <LibWeb/IndexedDB/IDBDatabase.h>
2025-03-24 20:43:12 +01:00
# include <LibWeb/IndexedDB/IDBObjectStore.h>
2025-01-08 23:18:56 +01:00
# include <LibWeb/IndexedDB/Internal/Algorithms.h>
2024-11-04 18:53:05 +01:00
namespace Web : : IndexedDB {
2024-11-15 04:01:23 +13:00
GC_DEFINE_ALLOCATOR ( IDBDatabase ) ;
2024-11-04 18:53:05 +01:00
IDBDatabase : : IDBDatabase ( JS : : Realm & realm , Database & db )
: EventTarget ( realm )
, m_name ( db . name ( ) )
, m_associated_database ( db )
{
2025-04-02 10:28:04 +02:00
m_uuid = MUST ( Crypto : : generate_random_uuid ( ) ) ;
2024-11-04 18:53:05 +01:00
db . associate ( * this ) ;
2025-03-27 22:22:16 +00:00
m_object_store_set = Vector < GC : : Ref < ObjectStore > > { db . object_stores ( ) } ;
2024-11-04 18:53:05 +01:00
}
IDBDatabase : : ~ IDBDatabase ( ) = default ;
2024-11-15 04:01:23 +13:00
GC : : Ref < IDBDatabase > IDBDatabase : : create ( JS : : Realm & realm , Database & db )
2024-11-04 18:53:05 +01:00
{
2024-11-14 05:50:17 +13:00
return realm . create < IDBDatabase > ( realm , db ) ;
2024-11-04 18:53:05 +01:00
}
void IDBDatabase : : initialize ( JS : : Realm & realm )
{
WEB_SET_PROTOTYPE_FOR_INTERFACE ( IDBDatabase ) ;
2025-04-20 16:22:57 +02:00
Base : : initialize ( realm ) ;
2024-11-04 18:53:05 +01:00
}
void IDBDatabase : : visit_edges ( Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
2025-03-24 21:38:50 +01:00
visitor . visit ( m_object_store_set ) ;
2024-11-04 18:53:05 +01:00
visitor . visit ( m_associated_database ) ;
2025-04-09 22:59:07 +02:00
visitor . visit ( m_transactions ) ;
2024-11-04 18:53:05 +01:00
}
void IDBDatabase : : set_onabort ( WebIDL : : CallbackType * event_handler )
{
set_event_handler_attribute ( HTML : : EventNames : : abort , event_handler ) ;
}
WebIDL : : CallbackType * IDBDatabase : : onabort ( )
{
return event_handler_attribute ( HTML : : EventNames : : abort ) ;
}
void IDBDatabase : : set_onerror ( WebIDL : : CallbackType * event_handler )
{
set_event_handler_attribute ( HTML : : EventNames : : error , event_handler ) ;
}
WebIDL : : CallbackType * IDBDatabase : : onerror ( )
{
return event_handler_attribute ( HTML : : EventNames : : error ) ;
}
void IDBDatabase : : set_onclose ( WebIDL : : CallbackType * event_handler )
{
set_event_handler_attribute ( HTML : : EventNames : : close , event_handler ) ;
}
WebIDL : : CallbackType * IDBDatabase : : onclose ( )
{
return event_handler_attribute ( HTML : : EventNames : : close ) ;
}
void IDBDatabase : : set_onversionchange ( WebIDL : : CallbackType * event_handler )
{
set_event_handler_attribute ( HTML : : EventNames : : versionchange , event_handler ) ;
}
WebIDL : : CallbackType * IDBDatabase : : onversionchange ( )
{
return event_handler_attribute ( HTML : : EventNames : : versionchange ) ;
}
2025-01-08 23:18:56 +01:00
// https://w3c.github.io/IndexedDB/#dom-idbdatabase-close
void IDBDatabase : : close ( )
{
// 1. Run close a database connection with this connection.
close_a_database_connection ( * this ) ;
}
2025-03-24 20:43:12 +01:00
// 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 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 )
2025-04-29 17:24:30 +02:00
return WebIDL : : TransactionInactiveError : : create ( realm , " Transaction is not active while creating object store " _string ) ;
2025-03-24 20:43:12 +01:00
// 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.
2025-03-24 21:59:58 +01:00
if ( database - > object_store_with_name ( name ) )
2025-03-24 20:43:12 +01:00
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 < 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.
2025-03-24 21:18:26 +01:00
auto object_store = ObjectStore : : create ( realm , database , name , auto_increment , key_path ) ;
2025-03-24 20:43:12 +01:00
2025-04-02 11:14:50 +02:00
// AD-HOC: Add newly created object store to this's object store set.
add_to_object_store_set ( object_store ) ;
2025-03-24 20:43:12 +01:00
// 10. Return a new object store handle associated with store and transaction.
return IDBObjectStore : : create ( realm , object_store , * transaction ) ;
}
2025-03-24 21:38:50 +01:00
// https://w3c.github.io/IndexedDB/#dom-idbdatabase-objectstorenames
GC : : Ref < HTML : : DOMStringList > IDBDatabase : : object_store_names ( )
{
// 1. Let names be a list of the names of the object stores in this's object store set.
Vector < String > names ;
for ( auto const & object_store : this - > object_store_set ( ) )
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 ) ;
}
2025-03-24 21:59:58 +01:00
// https://w3c.github.io/IndexedDB/#dom-idbdatabase-deleteobjectstore
WebIDL : : ExceptionOr < void > IDBDatabase : : delete_object_store ( String const & name )
{
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 )
2025-04-29 17:24:30 +02:00
return WebIDL : : TransactionInactiveError : : create ( realm , " Transaction is not active while deleting object store " _string ) ;
2025-03-24 21:59:58 +01:00
// 4. Let store be the object store named name in database, or throw a "NotFoundError" DOMException if none.
auto store = database - > object_store_with_name ( name ) ;
if ( ! store )
2025-04-10 00:25:13 +02:00
return WebIDL : : NotFoundError : : create ( realm , " Object store not found while trying to delete " _string ) ;
2025-03-24 21:59:58 +01:00
// 5. Remove store from this's object store set.
this - > remove_from_object_store_set ( * store ) ;
// FIXME: 6. If there is an object store handle associated with store and transaction, remove all entries from its index set.
// 7. Destroy store.
database - > remove_object_store ( * store ) ;
return { } ;
}
2025-04-25 18:08:40 +02:00
WebIDL : : ExceptionOr < GC : : Ref < IDBTransaction > > IDBDatabase : : transaction ( Variant < String , Vector < String > > store_names , Bindings : : IDBTransactionMode mode , IDBTransactionOptions options )
{
// 1. If a live upgrade transaction is associated with the connection, throw an "InvalidStateError" DOMException.
auto database = associated_database ( ) ;
if ( database - > upgrade_transaction ( ) )
return WebIDL : : InvalidStateError : : create ( realm ( ) , " Upgrade transaction is live " _string ) ;
// 2. If this's close pending flag is true, then throw an "InvalidStateError" DOMException.
if ( close_pending ( ) )
return WebIDL : : InvalidStateError : : create ( realm ( ) , " Close pending " _string ) ;
// 3. Let scope be the set of unique strings in storeNames if it is a sequence, or a set containing one string equal to storeNames otherwise.
Vector < String > scope ;
if ( store_names . has < Vector < String > > ( ) ) {
scope = store_names . get < Vector < String > > ( ) ;
} else {
scope . append ( store_names . get < String > ( ) ) ;
}
// 4. If any string in scope is not the name of an object store in the connected database, throw a "NotFoundError" DOMException.
for ( auto const & store_name : scope ) {
if ( ! database - > object_store_with_name ( store_name ) )
return WebIDL : : NotFoundError : : create ( realm ( ) , " Object store not found " _string ) ;
}
// 5. If scope is empty, throw an "InvalidAccessError" DOMException.
if ( scope . is_empty ( ) )
return WebIDL : : InvalidAccessError : : create ( realm ( ) , " Scope is empty " _string ) ;
// 6. If mode is not "readonly" or "readwrite", throw a TypeError.
if ( mode ! = Bindings : : IDBTransactionMode : : Readonly & & mode ! = Bindings : : IDBTransactionMode : : Readwrite )
return WebIDL : : SimpleException { WebIDL : : SimpleExceptionType : : TypeError , " Invalid transaction mode " _string } ;
// 7. Let transaction be a newly created transaction with this connection, mode, options’ durability member, and the set of object stores named in scope.
Vector < GC : : Ref < ObjectStore > > scope_stores ;
for ( auto const & store_name : scope ) {
auto store = database - > object_store_with_name ( store_name ) ;
scope_stores . append ( * store ) ;
}
auto transaction = IDBTransaction : : create ( realm ( ) , * this , mode , options . durability , scope_stores ) ;
// 8. Set transaction’ s cleanup event loop to the current event loop.
transaction - > set_cleanup_event_loop ( HTML : : main_thread_event_loop ( ) ) ;
// 9. Return an IDBTransaction object representing transaction.
return transaction ;
}
2024-11-04 18:53:05 +01:00
}