2024-05-19 17:58:06 +12:00
/*
* Copyright ( c ) 2024 , Shannon Booth < shannon @ serenityos . org >
2025-03-13 10:10:49 +01:00
* Copyright ( c ) 2024 - 2025 , stelar7 < dudedbz @ gmail . com >
2024-05-19 17:58:06 +12:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2025-03-13 10:10:49 +01:00
# include <AK/Vector.h>
# include <LibJS/Runtime/Array.h>
# include <LibJS/Runtime/Value.h>
2024-05-19 17:58:06 +12:00
# include <LibWeb/Bindings/IDBFactoryPrototype.h>
# include <LibWeb/Bindings/Intrinsics.h>
2024-11-04 18:19:19 +01:00
# include <LibWeb/DOM/Event.h>
# include <LibWeb/HTML/EventNames.h>
# include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
2024-11-04 19:10:07 +01:00
# include <LibWeb/IndexedDB/IDBDatabase.h>
2024-05-19 17:58:06 +12:00
# include <LibWeb/IndexedDB/IDBFactory.h>
2024-11-04 19:10:07 +01:00
# include <LibWeb/IndexedDB/Internal/Algorithms.h>
2024-11-11 10:49:44 +01:00
# include <LibWeb/IndexedDB/Internal/Key.h>
2024-11-04 18:19:19 +01:00
# include <LibWeb/Platform/EventLoopPlugin.h>
# include <LibWeb/StorageAPI/StorageKey.h>
2025-03-13 10:10:49 +01:00
# include <LibWeb/WebIDL/Promise.h>
2024-05-19 17:58:06 +12:00
namespace Web : : IndexedDB {
2024-11-15 04:01:23 +13:00
GC_DEFINE_ALLOCATOR ( IDBFactory ) ;
2024-05-19 17:58:06 +12:00
IDBFactory : : IDBFactory ( JS : : Realm & realm )
: Bindings : : PlatformObject ( realm )
{
}
IDBFactory : : ~ IDBFactory ( ) = default ;
void IDBFactory : : initialize ( JS : : Realm & realm )
{
WEB_SET_PROTOTYPE_FOR_INTERFACE ( IDBFactory ) ;
2025-04-20 16:22:57 +02:00
Base : : initialize ( realm ) ;
2024-05-19 17:58:06 +12:00
}
2024-11-04 18:19:19 +01:00
// https://w3c.github.io/IndexedDB/#dom-idbfactory-open
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < IDBOpenDBRequest > > IDBFactory : : open ( String const & name , Optional < u64 > version )
2024-11-04 18:19:19 +01:00
{
auto & realm = this - > realm ( ) ;
// 1. If version is 0 (zero), throw a TypeError.
if ( version . has_value ( ) & & version . value ( ) = = 0 )
return WebIDL : : SimpleException { WebIDL : : SimpleExceptionType : : TypeError , " The version provided must not be 0 " _string } ;
// 2. Let environment be this's relevant settings object.
auto & environment = HTML : : relevant_settings_object ( * this ) ;
// 3. Let storageKey be the result of running obtain a storage key given environment.
// If failure is returned, then throw a "SecurityError" DOMException and abort these steps.
auto storage_key = StorageAPI : : obtain_a_storage_key ( environment ) ;
if ( ! storage_key . has_value ( ) )
2025-08-07 19:31:52 -04:00
return WebIDL : : SecurityError : : create ( realm , " Failed to obtain a storage key " _utf16 ) ;
2024-11-04 18:19:19 +01:00
// 4. Let request be a new open request.
auto request = IDBOpenDBRequest : : create ( realm ) ;
// 5. Run these steps in parallel:
2024-11-15 04:01:23 +13:00
Platform : : EventLoopPlugin : : the ( ) . deferred_invoke ( GC : : create_function ( realm . heap ( ) , [ & realm , storage_key , name , version , request ] {
2024-11-04 19:10:07 +01:00
// 1. Let result be the result of opening a database connection, with storageKey, name, version if given and undefined otherwise, and request.
2025-09-09 20:41:59 +01:00
open_a_database_connection ( realm , storage_key . value ( ) , name , version , request , GC : : create_function ( realm . heap ( ) , [ & realm , request ] ( WebIDL : : ExceptionOr < GC : : Ref < IDBDatabase > > result ) {
// 2. Set request’ s processed flag to true.
request - > set_processed ( true ) ;
// 3. Queue a database task to run these steps:
queue_a_database_task ( GC : : create_function ( realm . heap ( ) , [ & realm , request , result = move ( result ) ] ( ) mutable {
// 1. If result is an error, then:
if ( result . is_error ( ) ) {
// 1. Set request’ s result to undefined.
request - > set_result ( JS : : js_undefined ( ) ) ;
// 2. Set request’ s error to result.
request - > set_error ( result . exception ( ) . get < GC : : Ref < WebIDL : : DOMException > > ( ) ) ;
// 3. Set request’ s done flag to true.
request - > set_done ( true ) ;
// 4. Fire an event named error at request with its bubbles and cancelable attributes initialized to true.
request - > dispatch_event ( DOM : : Event : : create ( realm , HTML : : EventNames : : error ) ) ;
} else {
// 1. Set request’ s result to result.
request - > set_result ( result . release_value ( ) ) ;
// 2. Set request’ s done flag to true.
request - > set_done ( true ) ;
// 3. Fire an event named success at request.
request - > dispatch_event ( DOM : : Event : : create ( realm , HTML : : EventNames : : success ) ) ;
}
} ) ) ;
2024-11-04 19:10:07 +01:00
} ) ) ;
2024-11-04 18:19:19 +01:00
} ) ) ;
// 6. Return a new IDBOpenDBRequest object for request.
return request ;
}
2024-11-11 10:49:44 +01:00
// https://w3c.github.io/IndexedDB/#dom-idbfactory-cmp
WebIDL : : ExceptionOr < i8 > IDBFactory : : cmp ( JS : : Value first , JS : : Value second )
{
// 1. Let a be the result of converting a value to a key with first. Rethrow any exceptions.
2025-04-19 17:08:59 +02:00
auto a = TRY ( convert_a_value_to_a_key ( realm ( ) , first ) ) ;
2024-11-11 10:49:44 +01:00
// 2. If a is invalid, throw a "DataError" DOMException.
2025-04-25 17:59:06 +02:00
if ( a - > is_invalid ( ) )
2025-08-07 19:31:52 -04:00
return WebIDL : : DataError : : create ( realm ( ) , " Failed to convert a value to a key " _utf16 ) ;
2024-11-11 10:49:44 +01:00
// 3. Let b be the result of converting a value to a key with second. Rethrow any exceptions.
2025-04-19 17:08:59 +02:00
auto b = TRY ( convert_a_value_to_a_key ( realm ( ) , second ) ) ;
2024-11-11 10:49:44 +01:00
// 4. If b is invalid, throw a "DataError" DOMException.
2025-04-25 17:59:06 +02:00
if ( b - > is_invalid ( ) )
2025-08-07 19:31:52 -04:00
return WebIDL : : DataError : : create ( realm ( ) , " Failed to convert a value to a key " _utf16 ) ;
2024-11-11 10:49:44 +01:00
// 5. Return the results of comparing two keys with a and b.
2025-04-25 17:59:06 +02:00
return Key : : compare_two_keys ( a , b ) ;
2024-11-11 10:49:44 +01:00
}
2024-12-01 21:12:35 +01:00
// https://w3c.github.io/IndexedDB/#dom-idbfactory-deletedatabase
WebIDL : : ExceptionOr < GC : : Ref < IDBOpenDBRequest > > IDBFactory : : delete_database ( String const & name )
{
auto & realm = this - > realm ( ) ;
// 1. Let environment be this's relevant settings object.
auto & environment = HTML : : relevant_settings_object ( * this ) ;
// 2. Let storageKey be the result of running obtain a storage key given environment.
// If failure is returned, then throw a "SecurityError" DOMException and abort these steps.
auto storage_key = StorageAPI : : obtain_a_storage_key ( environment ) ;
if ( ! storage_key . has_value ( ) )
2025-08-07 19:31:52 -04:00
return WebIDL : : SecurityError : : create ( realm , " Failed to obtain a storage key " _utf16 ) ;
2024-12-01 21:12:35 +01:00
// 3. Let request be a new open request.
auto request = IDBOpenDBRequest : : create ( realm ) ;
// 4. Run these steps in parallel:
Platform : : EventLoopPlugin : : the ( ) . deferred_invoke ( GC : : create_function ( realm . heap ( ) , [ & realm , storage_key , name , request ] {
// 1. Let result be the result of deleting a database, with storageKey, name, and request.
2025-09-09 20:41:59 +01:00
delete_a_database ( realm , storage_key . value ( ) , name , request , GC : : create_function ( realm . heap ( ) , [ & realm , request ] ( WebIDL : : ExceptionOr < u64 > result ) {
// 2. Set request’ s processed flag to true.
request - > set_processed ( true ) ;
// 3. Queue a database task to run these steps:
queue_a_database_task ( GC : : create_function ( realm . heap ( ) , [ & realm , request , result = move ( result ) ] ( ) mutable {
// 1. If result is an error,
if ( result . is_error ( ) ) {
// set request’ s error to result,
request - > set_error ( result . exception ( ) . get < GC : : Ref < WebIDL : : DOMException > > ( ) ) ;
// set request’ s done flag to true,
request - > set_done ( true ) ;
// and fire an event named error at request with its bubbles and cancelable attributes initialized to true.
request - > dispatch_event ( DOM : : Event : : create ( realm , HTML : : EventNames : : error , { . bubbles = true , . cancelable = true } ) ) ;
}
// 2. Otherwise,
else {
// set request’ s result to undefined,
request - > set_result ( JS : : js_undefined ( ) ) ;
// set request’ s done flag to true,
request - > set_done ( true ) ;
// and fire a version change event named success at request with result and null.
auto value = result . release_value ( ) ;
fire_a_version_change_event ( realm , HTML : : EventNames : : success , request , value , { } ) ;
}
} ) ) ;
2024-12-01 21:12:35 +01:00
} ) ) ;
} ) ) ;
// 5. Return a new IDBOpenDBRequest object for request.
return request ;
}
2025-03-13 10:10:49 +01:00
// https://w3c.github.io/IndexedDB/#dom-idbfactory-databases
GC : : Ref < WebIDL : : Promise > IDBFactory : : databases ( )
{
auto & realm = this - > realm ( ) ;
// 1. Let environment be this's relevant settings object.
auto & environment = HTML : : relevant_settings_object ( * this ) ;
// 2. Let storageKey be the result of running obtain a storage key given environment.
// If failure is returned, then return a promise rejected with a "SecurityError" DOMException
auto maybe_storage_key = StorageAPI : : obtain_a_storage_key ( environment ) ;
if ( ! maybe_storage_key . has_value ( ) )
2025-08-07 19:31:52 -04:00
return WebIDL : : create_rejected_promise_from_exception ( realm , WebIDL : : SecurityError : : create ( realm , " Failed to obtain a storage key " _utf16 ) ) ;
2025-03-13 10:10:49 +01:00
auto storage_key = maybe_storage_key . release_value ( ) ;
// 3. Let p be a new promise.
auto p = WebIDL : : create_promise ( realm ) ;
// 4. Run these steps in parallel:
Platform : : EventLoopPlugin : : the ( ) . deferred_invoke ( GC : : create_function ( realm . heap ( ) , [ & realm , storage_key , p ] ( ) {
HTML : : TemporaryExecutionContext context ( realm , HTML : : TemporaryExecutionContext : : CallbacksEnabled : : Yes ) ;
// 1. Let databases be the set of databases in storageKey.
// If this cannot be determined for any reason, then reject p with an appropriate error (e.g. an "UnknownError" DOMException) and terminate these steps.
auto databases = Database : : for_key ( storage_key ) ;
// 2. Let result be a new list.
auto result = MUST ( JS : : Array : : create ( realm , databases . size ( ) ) ) ;
// 3. For each db of databases:
for ( u32 i = 0 ; i < databases . size ( ) ; + + i ) {
auto & db = databases [ i ] ;
2025-04-25 09:13:52 +02:00
// 1. If db’ s version is 0, then continue.
if ( db - > version ( ) = = 0 )
continue ;
// 2. Let info be a new IDBDatabaseInfo dictionary.
2025-03-13 10:10:49 +01:00
auto info = JS : : Object : : create ( realm , realm . intrinsics ( ) . object_prototype ( ) ) ;
2025-04-25 09:13:52 +02:00
// 3. Set info’ s name dictionary member to db’ s name.
2025-08-02 19:27:29 -04:00
MUST ( info - > create_data_property ( " name " _utf16_fly_string , JS : : PrimitiveString : : create ( realm . vm ( ) , db - > name ( ) ) ) ) ;
2025-04-25 09:13:52 +02:00
// 4. Set info’ s version dictionary member to db’ s version.
2025-08-02 19:27:29 -04:00
MUST ( info - > create_data_property ( " version " _utf16_fly_string , JS : : Value ( db - > version ( ) ) ) ) ;
2025-03-13 10:10:49 +01:00
// 4. Append info to result.
MUST ( result - > create_data_property_or_throw ( i , info ) ) ;
}
// 4. Resolve p with result.
WebIDL : : resolve_promise ( realm , p , result ) ;
} ) ) ;
// 5. Return p.
return p ;
}
2024-05-19 17:58:06 +12:00
}