2024-11-04 19:03:26 +01:00
/*
2025-04-01 19:51:22 +02:00
* Copyright ( c ) 2024 - 2025 , stelar7 < dudedbz @ gmail . com >
2024-11-04 19:03:26 +01:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2025-04-11 11:25:56 +02:00
# include <AK/Math.h>
2025-05-08 23:36:18 +02:00
# include <AK/NumericLimits.h>
2025-03-24 21:23:58 +01:00
# include <AK/QuickSort.h>
2024-11-11 10:49:36 +01:00
# include <LibJS/Runtime/AbstractOperations.h>
# include <LibJS/Runtime/Array.h>
# include <LibJS/Runtime/ArrayBuffer.h>
2025-04-19 17:08:59 +02:00
# include <LibJS/Runtime/Completion.h>
2024-11-11 10:49:36 +01:00
# include <LibJS/Runtime/DataView.h>
# include <LibJS/Runtime/Date.h>
# include <LibJS/Runtime/TypedArray.h>
2024-11-04 19:03:26 +01:00
# include <LibJS/Runtime/VM.h>
2024-11-04 19:21:18 +01:00
# include <LibWeb/DOM/EventDispatcher.h>
2025-04-11 10:51:03 +02:00
# include <LibWeb/FileAPI/Blob.h>
# include <LibWeb/FileAPI/File.h>
2024-11-04 19:03:26 +01:00
# include <LibWeb/HTML/EventNames.h>
2025-04-09 23:11:12 +02:00
# include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
2025-04-11 10:38:55 +02:00
# include <LibWeb/HTML/StructuredSerialize.h>
2025-04-11 11:22:29 +02:00
# include <LibWeb/IndexedDB/IDBCursor.h>
2024-11-07 20:33:25 +01:00
# include <LibWeb/IndexedDB/IDBDatabase.h>
2025-04-11 11:22:29 +02:00
# include <LibWeb/IndexedDB/IDBIndex.h>
# include <LibWeb/IndexedDB/IDBObjectStore.h>
2024-11-04 19:03:26 +01:00
# include <LibWeb/IndexedDB/IDBRequest.h>
2024-11-07 20:54:13 +01:00
# include <LibWeb/IndexedDB/IDBTransaction.h>
2024-11-04 19:21:18 +01:00
# include <LibWeb/IndexedDB/IDBVersionChangeEvent.h>
2024-11-04 19:03:26 +01:00
# include <LibWeb/IndexedDB/Internal/Algorithms.h>
# include <LibWeb/IndexedDB/Internal/ConnectionQueueHandler.h>
# include <LibWeb/IndexedDB/Internal/Database.h>
2025-04-29 17:37:16 +02:00
# include <LibWeb/IndexedDB/Internal/Index.h>
# include <LibWeb/IndexedDB/Internal/Key.h>
2025-03-24 21:23:58 +01:00
# include <LibWeb/Infra/Strings.h>
2025-04-09 23:11:12 +02:00
# include <LibWeb/Platform/EventLoopPlugin.h>
2024-11-04 19:03:26 +01:00
# include <LibWeb/StorageAPI/StorageKey.h>
2024-11-11 10:49:36 +01:00
# include <LibWeb/WebIDL/AbstractOperations.h>
2025-03-01 10:07:55 +01:00
# include <LibWeb/WebIDL/Buffers.h>
2024-11-04 19:03:26 +01:00
namespace Web : : IndexedDB {
2025-04-11 11:25:56 +02:00
# if defined(AK_COMPILER_CLANG)
# define MAX_KEY_GENERATOR_VALUE AK::exp2(53.)
# else
constexpr double const MAX_KEY_GENERATOR_VALUE { __builtin_exp2 ( 53 ) } ;
# endif
2024-11-04 19:03:26 +01:00
// https://w3c.github.io/IndexedDB/#open-a-database-connection
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < IDBDatabase > > open_a_database_connection ( JS : : Realm & realm , StorageAPI : : StorageKey storage_key , String name , Optional < u64 > maybe_version , GC : : Ref < IDBRequest > request )
2024-11-04 19:03:26 +01:00
{
// 1. Let queue be the connection queue for storageKey and name.
auto & queue = ConnectionQueueHandler : : for_key_and_name ( storage_key , name ) ;
// 2. Add request to queue.
queue . append ( request ) ;
2025-04-02 10:28:04 +02:00
dbgln_if ( IDB_DEBUG , " open_a_database_connection: added request {} to queue " , request - > uuid ( ) ) ;
2024-11-04 19:03:26 +01:00
// 3. Wait until all previous requests in queue have been processed.
2024-11-15 04:01:23 +13:00
HTML : : main_thread_event_loop ( ) . spin_until ( GC : : create_function ( realm . vm ( ) . heap ( ) , [ queue , request ] ( ) {
2025-04-02 10:28:04 +02:00
if constexpr ( IDB_DEBUG ) {
dbgln ( " open_a_database_connection: waiting for step 3 " ) ;
dbgln ( " requests in queue: " ) ;
for ( auto const & item : queue ) {
dbgln ( " [{}] - {} = {} " , item = = request ? " x " sv : " " sv , item - > uuid ( ) , item - > processed ( ) ? " processed " sv : " not processed " sv ) ;
}
}
2024-11-04 19:03:26 +01:00
return queue . all_previous_requests_processed ( request ) ;
} ) ) ;
// 4. Let db be the database named name in storageKey, or null otherwise.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Database > db ;
2024-11-07 22:20:38 +01:00
auto maybe_db = Database : : for_key_and_name ( storage_key , name ) ;
if ( maybe_db . has_value ( ) ) {
db = maybe_db . value ( ) ;
}
2024-11-04 19:03:26 +01:00
// 5. If version is undefined, let version be 1 if db is null, or db’ s version otherwise.
auto version = maybe_version . value_or ( maybe_db . has_value ( ) ? maybe_db . value ( ) - > version ( ) : 1 ) ;
// 6. If db is null, let db be a new database with name name, version 0 (zero), and with no object stores.
// If this fails for any reason, return an appropriate error (e.g. a "QuotaExceededError" or "UnknownError" DOMException).
if ( ! maybe_db . has_value ( ) ) {
auto maybe_database = Database : : create_for_key_and_name ( realm , storage_key , name ) ;
if ( maybe_database . is_error ( ) ) {
return WebIDL : : OperationError : : create ( realm , " Unable to create a new database " _string ) ;
}
db = maybe_database . release_value ( ) ;
}
// 7. If db’ s version is greater than version, return a newly created "VersionError" DOMException and abort these steps.
if ( db - > version ( ) > version ) {
return WebIDL : : VersionError : : create ( realm , " Database version is greater than the requested version " _string ) ;
}
// 8. Let connection be a new connection to db.
auto connection = IDBDatabase : : create ( realm , * db ) ;
2025-04-02 10:28:04 +02:00
dbgln_if ( IDB_DEBUG , " Created new connection with UUID: {} " , connection - > uuid ( ) ) ;
2024-11-04 19:03:26 +01:00
// 9. Set connection’ s version to version.
connection - > set_version ( version ) ;
// 10. If db’ s version is less than version, then:
if ( db - > version ( ) < version ) {
// 1. Let openConnections be the set of all connections, except connection, associated with db.
auto open_connections = db - > associated_connections_except ( connection ) ;
2024-11-07 20:44:00 +01:00
// 2. For each entry of openConnections that does not have its close pending flag set to true,
2025-05-16 08:26:03 +02:00
// queue a database task to fire a version change event named versionchange at entry with db’ s version and version.
2024-12-09 19:47:09 -07:00
IGNORE_USE_IN_ESCAPING_LAMBDA u32 events_to_fire = open_connections . size ( ) ;
IGNORE_USE_IN_ESCAPING_LAMBDA u32 events_fired = 0 ;
2024-12-01 21:11:57 +01:00
for ( auto const & entry : open_connections ) {
2024-11-04 19:03:26 +01:00
if ( ! entry - > close_pending ( ) ) {
2025-05-16 08:26:03 +02:00
queue_a_database_task ( GC : : create_function ( realm . vm ( ) . heap ( ) , [ & realm , entry , db , version , & events_fired ] ( ) {
2024-11-04 19:21:18 +01:00
fire_a_version_change_event ( realm , HTML : : EventNames : : versionchange , * entry , db - > version ( ) , version ) ;
2024-11-08 17:38:26 +01:00
events_fired + + ;
2024-11-04 19:21:18 +01:00
} ) ) ;
2024-11-08 17:38:26 +01:00
} else {
events_fired + + ;
2024-11-04 19:03:26 +01:00
}
}
2024-11-08 17:38:26 +01:00
// 3. Wait for all of the events to be fired.
HTML : : main_thread_event_loop ( ) . spin_until ( GC : : create_function ( realm . vm ( ) . heap ( ) , [ & events_to_fire , & events_fired ] ( ) {
2025-04-02 10:28:04 +02:00
if constexpr ( IDB_DEBUG ) {
dbgln ( " open_a_database_connection: waiting for step 10.3 " ) ;
dbgln ( " events_fired: {}, events_to_fire: {} " , events_fired , events_to_fire ) ;
}
2024-11-08 17:38:26 +01:00
return events_fired = = events_to_fire ;
} ) ) ;
2024-11-04 19:03:26 +01:00
2024-11-07 20:44:00 +01:00
// 4. If any of the connections in openConnections are still not closed,
2025-05-16 08:26:03 +02:00
// queue a database task to fire a version change event named blocked at request with db’ s version and version.
2024-12-01 21:11:57 +01:00
for ( auto const & entry : open_connections ) {
2024-11-04 19:03:26 +01:00
if ( entry - > state ( ) ! = IDBDatabase : : ConnectionState : : Closed ) {
2025-05-16 08:26:03 +02:00
queue_a_database_task ( GC : : create_function ( realm . vm ( ) . heap ( ) , [ & realm , entry , db , version ] ( ) {
2024-11-04 19:21:18 +01:00
fire_a_version_change_event ( realm , HTML : : EventNames : : blocked , * entry , db - > version ( ) , version ) ;
} ) ) ;
2024-11-04 19:03:26 +01:00
}
}
// 5. Wait until all connections in openConnections are closed.
2024-11-15 04:01:23 +13:00
HTML : : main_thread_event_loop ( ) . spin_until ( GC : : create_function ( realm . vm ( ) . heap ( ) , [ open_connections ] ( ) {
2025-04-02 10:28:04 +02:00
if constexpr ( IDB_DEBUG ) {
dbgln ( " open_a_database_connection: waiting for step 10.5 " ) ;
dbgln ( " open connections: {} " , open_connections . size ( ) ) ;
for ( auto const & connection : open_connections ) {
dbgln ( " - {} " , connection - > uuid ( ) ) ;
}
}
2024-11-04 19:03:26 +01:00
for ( auto const & entry : open_connections ) {
if ( entry - > state ( ) ! = IDBDatabase : : ConnectionState : : Closed ) {
return false ;
}
}
return true ;
} ) ) ;
2024-11-07 20:54:13 +01:00
// 6. Run upgrade a database using connection, version and request.
2025-03-21 12:18:24 +01:00
// AD-HOC: https://github.com/w3c/IndexedDB/issues/433#issuecomment-2512330086
auto upgrade_transaction = upgrade_a_database ( realm , connection , version , request ) ;
2024-11-04 19:03:26 +01:00
// 7. If connection was closed, return a newly created "AbortError" DOMException and abort these steps.
2025-04-09 22:51:11 +02:00
if ( connection - > state ( ) = = IDBDatabase : : ConnectionState : : Closed )
2024-11-04 19:03:26 +01:00
return WebIDL : : AbortError : : create ( realm , " Connection was closed " _string ) ;
2024-12-01 22:52:33 +01:00
// 8. If the upgrade transaction was aborted, run the steps to close a database connection with connection,
// return a newly created "AbortError" DOMException and abort these steps.
2025-03-21 12:18:24 +01:00
if ( upgrade_transaction - > aborted ( ) ) {
2025-04-09 22:51:11 +02:00
close_a_database_connection ( * connection ) ;
2024-12-01 22:52:33 +01:00
return WebIDL : : AbortError : : create ( realm , " Upgrade transaction was aborted " _string ) ;
}
2024-11-04 19:03:26 +01:00
}
// 11. Return connection.
return connection ;
}
2024-11-15 04:01:23 +13:00
bool fire_a_version_change_event ( JS : : Realm & realm , FlyString const & event_name , GC : : Ref < DOM : : EventTarget > target , u64 old_version , Optional < u64 > new_version )
2024-11-04 19:21:18 +01:00
{
IDBVersionChangeEventInit event_init = { } ;
// 4. Set event’ s oldVersion attribute to oldVersion.
event_init . old_version = old_version ;
// 5. Set event’ s newVersion attribute to newVersion.
event_init . new_version = new_version ;
// 1. Let event be the result of creating an event using IDBVersionChangeEvent.
// 2. Set event’ s type attribute to e.
auto event = IDBVersionChangeEvent : : create ( realm , event_name , event_init ) ;
// 3. Set event’ s bubbles and cancelable attributes to false.
event - > set_bubbles ( false ) ;
event - > set_cancelable ( false ) ;
// 6. Let legacyOutputDidListenersThrowFlag be false.
auto legacy_output_did_listeners_throw_flag = false ;
// 7. Dispatch event at target with legacyOutputDidListenersThrowFlag.
2024-11-07 22:59:03 +01:00
DOM : : EventDispatcher : : dispatch ( target , * event , false , legacy_output_did_listeners_throw_flag ) ;
2024-11-04 19:21:18 +01:00
// 8. Return legacyOutputDidListenersThrowFlag.
return legacy_output_did_listeners_throw_flag ;
}
2024-11-11 10:49:36 +01:00
// https://w3c.github.io/IndexedDB/#convert-value-to-key
2025-04-25 17:59:06 +02:00
WebIDL : : ExceptionOr < GC : : Ref < Key > > convert_a_value_to_a_key ( JS : : Realm & realm , JS : : Value input , Vector < JS : : Value > seen )
2024-11-11 10:49:36 +01:00
{
// 1. If seen was not given, then let seen be a new empty set.
// NOTE: This is handled by the caller.
// 2. If seen contains input, then return invalid.
if ( seen . contains_slow ( input ) )
2025-04-25 17:59:06 +02:00
return Key : : create_invalid ( realm , " Already seen key " _string ) ;
2024-11-11 10:49:36 +01:00
// 3. Jump to the appropriate step below:
// - If Type(input) is Number
if ( input . is_number ( ) ) {
// 1. If input is NaN then return invalid.
if ( input . is_nan ( ) )
2025-04-25 17:59:06 +02:00
return Key : : create_invalid ( realm , " NaN key " _string ) ;
2024-11-11 10:49:36 +01:00
// 2. Otherwise, return a new key with type number and value input.
2025-01-08 23:09:56 +01:00
return Key : : create_number ( realm , input . as_double ( ) ) ;
2024-11-11 10:49:36 +01:00
}
// - If input is a Date (has a [[DateValue]] internal slot)
if ( input . is_object ( ) & & is < JS : : Date > ( input . as_object ( ) ) ) {
// 1. Let ms be the value of input’ s [[DateValue]] internal slot.
auto & date = static_cast < JS : : Date & > ( input . as_object ( ) ) ;
auto ms = date . date_value ( ) ;
// 2. If ms is NaN then return invalid.
if ( isnan ( ms ) )
2025-04-25 17:59:06 +02:00
return Key : : create_invalid ( realm , " NaN key " _string ) ;
2024-11-11 10:49:36 +01:00
// 3. Otherwise, return a new key with type date and value ms.
2025-01-08 23:09:56 +01:00
return Key : : create_date ( realm , ms ) ;
2024-11-11 10:49:36 +01:00
}
// - If Type(input) is String
if ( input . is_string ( ) ) {
// 1. Return a new key with type string and value input.
2025-01-08 23:09:56 +01:00
return Key : : create_string ( realm , input . as_string ( ) . utf8_string ( ) ) ;
2024-11-11 10:49:36 +01:00
}
// - If input is a buffer source type
if ( input . is_object ( ) & & ( is < JS : : TypedArrayBase > ( input . as_object ( ) ) | | is < JS : : ArrayBuffer > ( input . as_object ( ) ) | | is < JS : : DataView > ( input . as_object ( ) ) ) ) {
2025-04-25 09:14:25 +02:00
// 1. If input is detached then return invalid.
2025-03-01 10:07:55 +01:00
if ( WebIDL : : is_buffer_source_detached ( input ) )
2025-04-25 17:59:06 +02:00
return Key : : create_invalid ( realm , " Detached buffer is not supported as key " _string ) ;
2025-03-01 10:07:55 +01:00
// 2. Let bytes be the result of getting a copy of the bytes held by the buffer source input.
2025-04-25 17:59:06 +02:00
auto data_buffer = MUST ( WebIDL : : get_buffer_source_copy ( input . as_object ( ) ) ) ;
2024-11-11 10:49:36 +01:00
2025-03-01 10:07:55 +01:00
// 3. Return a new key with type binary and value bytes.
2025-01-08 23:09:56 +01:00
return Key : : create_binary ( realm , data_buffer ) ;
2024-11-11 10:49:36 +01:00
}
// - If input is an Array exotic object
if ( input . is_object ( ) & & is < JS : : Array > ( input . as_object ( ) ) ) {
// 1. Let len be ? ToLength( ? Get(input, "length")).
2025-04-19 17:08:59 +02:00
auto length = TRY ( length_of_array_like ( realm . vm ( ) , input . as_object ( ) ) ) ;
2024-11-11 10:49:36 +01:00
// 2. Append input to seen.
seen . append ( input ) ;
// 3. Let keys be a new empty list.
2025-01-08 23:09:56 +01:00
Vector < GC : : Root < Key > > keys ;
2024-11-11 10:49:36 +01:00
// 4. Let index be 0.
u64 index = 0 ;
// 5. While index is less than len:
while ( index < length ) {
// 1. Let hop be ? HasOwnProperty(input, index).
2025-04-19 17:08:59 +02:00
auto hop = TRY ( input . as_object ( ) . has_own_property ( index ) ) ;
2024-11-11 10:49:36 +01:00
// 2. If hop is false, return invalid.
if ( ! hop )
2025-04-25 17:59:06 +02:00
return Key : : create_invalid ( realm , " Array-like object has no property " _string ) ;
2024-11-11 10:49:36 +01:00
// 3. Let entry be ? Get(input, index).
2025-04-19 17:08:59 +02:00
auto entry = TRY ( input . as_object ( ) . get ( index ) ) ;
2024-11-11 10:49:36 +01:00
// 4. Let key be the result of converting a value to a key with arguments entry and seen.
// 5. ReturnIfAbrupt(key).
2025-04-25 17:59:06 +02:00
auto key = TRY ( convert_a_value_to_a_key ( realm , entry , seen ) ) ;
2024-11-11 10:49:36 +01:00
// 6. If key is invalid abort these steps and return invalid.
2025-04-25 17:59:06 +02:00
if ( key - > is_invalid ( ) )
return key ;
2024-11-11 10:49:36 +01:00
// 7. Append key to keys.
keys . append ( key ) ;
// 8. Increase index by 1.
index + + ;
}
// 6. Return a new array key with value keys.
2025-01-08 23:09:56 +01:00
return Key : : create_array ( realm , keys ) ;
2024-11-11 10:49:36 +01:00
}
// - Otherwise
2025-04-25 17:59:06 +02:00
// Return invalid.
return Key : : create_invalid ( realm , " Unable to convert value to key. Its not of a known type " _string ) ;
2024-11-11 10:49:36 +01:00
}
2024-11-07 20:33:25 +01:00
// https://w3c.github.io/IndexedDB/#close-a-database-connection
2025-04-09 22:53:43 +02:00
void close_a_database_connection ( GC : : Ref < IDBDatabase > connection , bool forced )
2024-11-07 20:33:25 +01:00
{
2025-04-09 22:59:34 +02:00
auto & realm = connection - > realm ( ) ;
2024-11-07 20:33:25 +01:00
// 1. Set connection’ s close pending flag to true.
2025-04-09 22:53:43 +02:00
connection - > set_close_pending ( true ) ;
2024-11-07 20:33:25 +01:00
2025-04-09 22:59:34 +02:00
// 2. If the forced flag is true, then for each transaction created using connection run abort a transaction with transaction and newly created "AbortError" DOMException.
if ( forced ) {
for ( auto const & transaction : connection - > transactions ( ) ) {
abort_a_transaction ( * transaction , WebIDL : : AbortError : : create ( realm , " Connection was closed " _string ) ) ;
}
}
// 3. Wait for all transactions created using connection to complete. Once they are complete, connection is closed.
HTML : : main_thread_event_loop ( ) . spin_until ( GC : : create_function ( realm . vm ( ) . heap ( ) , [ connection ] ( ) {
if constexpr ( IDB_DEBUG ) {
dbgln ( " close_a_database_connection: waiting for step 3 " ) ;
dbgln ( " transactions created using connection: " ) ;
for ( auto const & transaction : connection - > transactions ( ) ) {
dbgln ( " - {} - {} " , transaction - > uuid ( ) , ( u8 ) transaction - > state ( ) ) ;
}
}
for ( auto const & transaction : connection - > transactions ( ) ) {
if ( ! transaction - > is_finished ( ) )
return false ;
}
return true ;
} ) ) ;
2025-04-09 22:53:43 +02:00
connection - > set_state ( IDBDatabase : : ConnectionState : : Closed ) ;
2024-11-07 20:33:25 +01:00
// 4. If the forced flag is true, then fire an event named close at connection.
if ( forced )
2025-04-09 22:59:34 +02:00
connection - > dispatch_event ( DOM : : Event : : create ( realm , HTML : : EventNames : : close ) ) ;
2024-11-07 20:33:25 +01:00
}
2024-12-01 23:40:00 +01:00
// https://w3c.github.io/IndexedDB/#upgrade-a-database
2025-03-21 12:18:24 +01:00
GC : : Ref < IDBTransaction > upgrade_a_database ( JS : : Realm & realm , GC : : Ref < IDBDatabase > connection , u64 version , GC : : Ref < IDBRequest > request )
2024-11-07 20:54:13 +01:00
{
// 1. Let db be connection’ s database.
auto db = connection - > associated_database ( ) ;
// 2. Let transaction be a new upgrade transaction with connection used as connection.
2025-04-01 19:51:22 +02:00
// 3. Set transaction’ s scope to connection’ s object store set.
auto transaction = IDBTransaction : : create ( realm , connection , Bindings : : IDBTransactionMode : : Versionchange , Bindings : : IDBTransactionDurability : : Default , Vector < GC : : Ref < ObjectStore > > { connection - > object_store_set ( ) } ) ;
2025-04-02 10:28:04 +02:00
dbgln_if ( IDB_DEBUG , " Created new upgrade transaction with UUID: {} " , transaction - > uuid ( ) ) ;
2024-11-07 20:54:13 +01:00
// 4. Set db’ s upgrade transaction to transaction.
db - > set_upgrade_transaction ( transaction ) ;
// 5. Set transaction’ s state to inactive.
transaction - > set_state ( IDBTransaction : : TransactionState : : Inactive ) ;
// FIXME: 6. Start transaction.
// 7. Let old version be db’ s version.
auto old_version = db - > version ( ) ;
// 8. Set db’ s version to version. This change is considered part of the transaction, and so if the transaction is aborted, this change is reverted.
db - > set_version ( version ) ;
// 9. Set request’ s processed flag to true.
request - > set_processed ( true ) ;
2025-05-16 08:26:03 +02:00
// 10. Queue a database task to run these steps:
queue_a_database_task ( GC : : create_function ( realm . vm ( ) . heap ( ) , [ & realm , request , connection , transaction , old_version , version ] ( ) {
2024-11-07 20:54:13 +01:00
// 1. Set request’ s result to connection.
request - > set_result ( connection ) ;
// 2. Set request’ s transaction to transaction.
2024-12-01 21:57:32 +01:00
// NOTE: We need to do a two-way binding here.
2024-11-07 20:54:13 +01:00
request - > set_transaction ( transaction ) ;
2024-12-01 21:57:32 +01:00
transaction - > set_associated_request ( request ) ;
2024-11-07 20:54:13 +01:00
// 3. Set request’ s done flag to true.
request - > set_done ( true ) ;
// 4. Set transaction’ s state to active.
transaction - > set_state ( IDBTransaction : : TransactionState : : Active ) ;
// 5. Let didThrow be the result of firing a version change event named upgradeneeded at request with old version and version.
2025-03-21 12:06:07 +01:00
auto did_throw = fire_a_version_change_event ( realm , HTML : : EventNames : : upgradeneeded , request , old_version , version ) ;
2024-11-07 20:54:13 +01:00
2025-04-25 09:14:25 +02:00
// 6. If transaction’ s state is active, then:
if ( transaction - > state ( ) = = IDBTransaction : : TransactionState : : Active ) {
2025-04-10 00:33:05 +02:00
2025-04-25 09:14:25 +02:00
// 1. Set transaction’ s state to inactive.
2025-04-10 00:33:05 +02:00
transaction - > set_state ( IDBTransaction : : TransactionState : : Inactive ) ;
2025-04-25 09:14:25 +02:00
// 2. If didThrow is true, run abort a transaction with transaction and a newly created "AbortError" DOMException.
2025-04-10 00:33:05 +02:00
if ( did_throw )
abort_a_transaction ( transaction , WebIDL : : AbortError : : create ( realm , " Version change event threw an exception " _string ) ) ;
// AD-HOC:
// The implementation must attempt to commit a transaction when all requests placed against the transaction have completed
// and their returned results handled,
// no new requests have been placed against the transaction,
// and the transaction has not been aborted.
if ( transaction - > state ( ) = = IDBTransaction : : TransactionState : : Inactive & & transaction - > request_list ( ) . is_empty ( ) & & ! transaction - > aborted ( ) )
commit_a_transaction ( realm , transaction ) ;
}
2024-11-07 20:54:13 +01:00
} ) ) ;
2024-12-01 22:02:23 +01:00
// 11. Wait for transaction to finish.
2025-04-09 23:15:39 +02:00
HTML : : main_thread_event_loop ( ) . spin_until ( GC : : create_function ( realm . vm ( ) . heap ( ) , [ transaction ] ( ) {
2025-04-02 10:28:04 +02:00
dbgln_if ( IDB_DEBUG , " upgrade_a_database: waiting for step 11 " ) ;
2025-04-09 23:15:39 +02:00
return transaction - > is_finished ( ) ;
2024-12-01 22:02:23 +01:00
} ) ) ;
2025-03-21 12:18:24 +01:00
return transaction ;
2024-11-07 20:54:13 +01:00
}
2024-12-01 21:10:28 +01:00
// https://w3c.github.io/IndexedDB/#deleting-a-database
WebIDL : : ExceptionOr < u64 > delete_a_database ( JS : : Realm & realm , StorageAPI : : StorageKey storage_key , String name , GC : : Ref < IDBRequest > request )
{
// 1. Let queue be the connection queue for storageKey and name.
auto & queue = ConnectionQueueHandler : : for_key_and_name ( storage_key , name ) ;
// 2. Add request to queue.
queue . append ( request ) ;
2025-04-02 10:28:04 +02:00
dbgln_if ( IDB_DEBUG , " delete_a_database: added request {} to queue " , request - > uuid ( ) ) ;
2024-12-01 21:10:28 +01:00
// 3. Wait until all previous requests in queue have been processed.
HTML : : main_thread_event_loop ( ) . spin_until ( GC : : create_function ( realm . vm ( ) . heap ( ) , [ queue , request ] ( ) {
2025-04-02 10:28:04 +02:00
if constexpr ( IDB_DEBUG ) {
dbgln ( " delete_a_database: waiting for step 3 " ) ;
dbgln ( " requests in queue: " ) ;
for ( auto const & item : queue ) {
dbgln ( " [{}] - {} = {} " , item = = request ? " x " sv : " " sv , item - > uuid ( ) , item - > processed ( ) ? " processed " sv : " not processed " sv ) ;
}
}
2024-12-01 21:10:28 +01:00
return queue . all_previous_requests_processed ( request ) ;
} ) ) ;
// 4. Let db be the database named name in storageKey, if one exists. Otherwise, return 0 (zero).
auto maybe_db = Database : : for_key_and_name ( storage_key , name ) ;
if ( ! maybe_db . has_value ( ) )
return 0 ;
auto db = maybe_db . value ( ) ;
// 5. Let openConnections be the set of all connections associated with db.
auto open_connections = db - > associated_connections ( ) ;
// 6. For each entry of openConnections that does not have its close pending flag set to true,
2025-05-16 08:26:03 +02:00
// queue a database task to fire a version change event named versionchange at entry with db’ s version and null.
2024-12-01 21:10:28 +01:00
IGNORE_USE_IN_ESCAPING_LAMBDA u32 events_to_fire = open_connections . size ( ) ;
IGNORE_USE_IN_ESCAPING_LAMBDA u32 events_fired = 0 ;
for ( auto const & entry : open_connections ) {
if ( ! entry - > close_pending ( ) ) {
2025-05-16 08:26:03 +02:00
queue_a_database_task ( GC : : create_function ( realm . vm ( ) . heap ( ) , [ & realm , entry , db , & events_fired ] ( ) {
2024-12-01 21:10:28 +01:00
fire_a_version_change_event ( realm , HTML : : EventNames : : versionchange , * entry , db - > version ( ) , { } ) ;
events_fired + + ;
} ) ) ;
} else {
events_fired + + ;
}
}
// 7. Wait for all of the events to be fired.
HTML : : main_thread_event_loop ( ) . spin_until ( GC : : create_function ( realm . vm ( ) . heap ( ) , [ & events_to_fire , & events_fired ] ( ) {
2025-04-02 10:28:04 +02:00
if constexpr ( IDB_DEBUG ) {
dbgln ( " delete_a_database: waiting for step 7 " ) ;
dbgln ( " events_fired: {}, events_to_fire: {} " , events_fired , events_to_fire ) ;
}
2024-12-01 21:10:28 +01:00
return events_fired = = events_to_fire ;
} ) ) ;
2025-05-16 08:26:03 +02:00
// 8. If any of the connections in openConnections are still not closed, queue a database task to fire a version change event named blocked at request with db’ s version and null.
2024-12-01 21:10:28 +01:00
for ( auto const & entry : open_connections ) {
if ( entry - > state ( ) ! = IDBDatabase : : ConnectionState : : Closed ) {
2025-05-16 08:26:03 +02:00
queue_a_database_task ( GC : : create_function ( realm . vm ( ) . heap ( ) , [ & realm , entry , db ] ( ) {
2024-12-01 21:10:28 +01:00
fire_a_version_change_event ( realm , HTML : : EventNames : : blocked , * entry , db - > version ( ) , { } ) ;
} ) ) ;
}
}
// 9. Wait until all connections in openConnections are closed.
HTML : : main_thread_event_loop ( ) . spin_until ( GC : : create_function ( realm . vm ( ) . heap ( ) , [ open_connections ] ( ) {
2025-04-02 10:28:04 +02:00
if constexpr ( IDB_DEBUG ) {
dbgln ( " delete_a_database: waiting for step 9 " ) ;
dbgln ( " open connections: {} " , open_connections . size ( ) ) ;
for ( auto const & connection : open_connections ) {
dbgln ( " - {} " , connection - > uuid ( ) ) ;
}
}
2024-12-01 21:10:28 +01:00
for ( auto const & entry : open_connections ) {
if ( entry - > state ( ) ! = IDBDatabase : : ConnectionState : : Closed ) {
return false ;
}
}
return true ;
} ) ) ;
// 10. Let version be db’ s version.
auto version = db - > version ( ) ;
// 11. Delete db. If this fails for any reason, return an appropriate error (e.g. "QuotaExceededError" or "UnknownError" DOMException).
auto maybe_deleted = Database : : delete_for_key_and_name ( storage_key , name ) ;
if ( maybe_deleted . is_error ( ) )
return WebIDL : : OperationError : : create ( realm , " Unable to delete database " _string ) ;
// 12. Return version.
return version ;
}
2024-12-01 21:57:32 +01:00
// https://w3c.github.io/IndexedDB/#abort-a-transaction
2025-04-02 10:28:34 +02:00
void abort_a_transaction ( GC : : Ref < IDBTransaction > transaction , GC : : Ptr < WebIDL : : DOMException > error )
2024-12-01 21:57:32 +01:00
{
2024-12-01 22:52:33 +01:00
// NOTE: This is not spec'ed anywhere, but we need to know IF the transaction was aborted.
2025-04-02 10:28:34 +02:00
transaction - > set_aborted ( true ) ;
2025-04-02 10:28:04 +02:00
dbgln_if ( IDB_DEBUG , " abort_a_transaction: transaction {} is aborting " , transaction - > uuid ( ) ) ;
2024-12-01 22:52:33 +01:00
2024-12-01 21:57:32 +01:00
// FIXME: 1. All the changes made to the database by the transaction are reverted.
// For upgrade transactions this includes changes to the set of object stores and indexes, as well as the change to the version.
// Any object stores and indexes which were created during the transaction are now considered deleted for the purposes of other algorithms.
// FIXME: 2. If transaction is an upgrade transaction, run the steps to abort an upgrade transaction with transaction.
// if (transaction.is_upgrade_transaction())
// abort_an_upgrade_transaction(transaction);
// 3. Set transaction’ s state to finished.
2025-04-02 10:28:34 +02:00
transaction - > set_state ( IDBTransaction : : TransactionState : : Finished ) ;
2024-12-01 21:57:32 +01:00
// 4. If error is not null, set transaction’ s error to error.
if ( error )
2025-04-02 10:28:34 +02:00
transaction - > set_error ( error ) ;
2024-12-01 21:57:32 +01:00
2025-04-09 23:18:18 +02:00
// 5. For each request of transaction’ s request list,
for ( auto const & request : transaction - > request_list ( ) ) {
// FIXME: abort the steps to asynchronously execute a request for request,
// set request’ s processed flag to true
request - > set_processed ( true ) ;
2025-05-16 08:26:03 +02:00
// and queue a database task to run these steps:
queue_a_database_task ( GC : : create_function ( transaction - > realm ( ) . vm ( ) . heap ( ) , [ request ] ( ) {
2025-04-09 23:18:18 +02:00
// 1. Set request’ s done flag to true.
request - > set_done ( true ) ;
// 2. Set request’ s result to undefined.
request - > set_result ( JS : : js_undefined ( ) ) ;
// 3. Set request’ s error to a newly created "AbortError" DOMException.
request - > set_error ( WebIDL : : AbortError : : create ( request - > realm ( ) , " Transaction was aborted " _string ) ) ;
// 4. Fire an event named error at request with its bubbles and cancelable attributes initialized to true.
request - > dispatch_event ( DOM : : Event : : create ( request - > realm ( ) , HTML : : EventNames : : error , { . bubbles = true , . cancelable = true } ) ) ;
} ) ) ;
}
2024-12-01 21:57:32 +01:00
2025-05-16 08:26:03 +02:00
// 6. Queue a database task to run these steps:
queue_a_database_task ( GC : : create_function ( transaction - > realm ( ) . vm ( ) . heap ( ) , [ transaction ] ( ) {
2024-12-01 21:57:32 +01:00
// 1. If transaction is an upgrade transaction, then set transaction’ s connection's associated database's upgrade transaction to null.
2025-04-02 10:28:34 +02:00
if ( transaction - > is_upgrade_transaction ( ) )
transaction - > connection ( ) - > associated_database ( ) - > set_upgrade_transaction ( nullptr ) ;
2024-12-01 21:57:32 +01:00
// 2. Fire an event named abort at transaction with its bubbles attribute initialized to true.
2025-04-02 10:28:34 +02:00
transaction - > dispatch_event ( DOM : : Event : : create ( transaction - > realm ( ) , HTML : : EventNames : : abort , { . bubbles = true } ) ) ;
2024-12-01 21:57:32 +01:00
// 3. If transaction is an upgrade transaction, then:
2025-04-02 10:28:34 +02:00
if ( transaction - > is_upgrade_transaction ( ) ) {
2024-12-01 21:57:32 +01:00
// 1. Let request be the open request associated with transaction.
2025-04-02 10:28:34 +02:00
auto request = transaction - > associated_request ( ) ;
2024-12-01 21:57:32 +01:00
// 2. Set request’ s transaction to null.
// NOTE: Clear the two-way binding.
request - > set_transaction ( nullptr ) ;
2025-04-02 10:28:34 +02:00
transaction - > set_associated_request ( nullptr ) ;
2024-12-01 21:57:32 +01:00
// 3. Set request’ s result to undefined.
request - > set_result ( JS : : js_undefined ( ) ) ;
// 4. Set request’ s processed flag to false.
2025-04-02 11:14:28 +02:00
// FIXME: request->set_processed(false);
2024-12-01 21:57:32 +01:00
// 5. Set request’ s done flag to false.
request - > set_done ( false ) ;
}
} ) ) ;
}
2025-01-08 23:25:46 +01:00
// https://w3c.github.io/IndexedDB/#convert-a-key-to-a-value
JS : : Value convert_a_key_to_a_value ( JS : : Realm & realm , GC : : Ref < Key > key )
{
// 1. Let type be key’ s type.
auto type = key - > type ( ) ;
// 2. Let value be key’ s value.
auto value = key - > value ( ) ;
// 3. Switch on type:
switch ( type ) {
case Key : : KeyType : : Number : {
// Return an ECMAScript Number value equal to value
return JS : : Value ( key - > value_as_double ( ) ) ;
}
case Key : : KeyType : : String : {
// Return an ECMAScript String value equal to value
return JS : : PrimitiveString : : create ( realm . vm ( ) , key - > value_as_string ( ) ) ;
}
case Key : : KeyType : : Date : {
// 1. Let date be the result of executing the ECMAScript Date constructor with the single argument value.
auto date = JS : : Date : : create ( realm , key - > value_as_double ( ) ) ;
// 2. Assert: date is not an abrupt completion.
// NOTE: This is not possible in our implementation.
// 3. Return date.
return date ;
}
case Key : : KeyType : : Binary : {
auto buffer = key - > value_as_byte_buffer ( ) ;
// 1. Let len be value’ s length.
auto len = buffer . size ( ) ;
// 2. Let buffer be the result of executing the ECMAScript ArrayBuffer constructor with len.
// 3. Assert: buffer is not an abrupt completion.
auto array_buffer = MUST ( JS : : ArrayBuffer : : create ( realm , len ) ) ;
// 4. Set the entries in buffer’ s [[ArrayBufferData]] internal slot to the entries in value.
buffer . span ( ) . copy_to ( array_buffer - > buffer ( ) ) ;
// 5. Return buffer.
return array_buffer ;
}
case Key : : KeyType : : Array : {
auto data = key - > value_as_vector ( ) ;
// 1. Let array be the result of executing the ECMAScript Array constructor with no arguments.
// 2. Assert: array is not an abrupt completion.
auto array = MUST ( JS : : Array : : create ( realm , 0 ) ) ;
// 3. Let len be value’ s size.
auto len = data . size ( ) ;
// 4. Let index be 0.
u64 index = 0 ;
// 5. While index is less than len:
while ( index < len ) {
// 1. Let entry be the result of converting a key to a value with value[index].
auto entry = convert_a_key_to_a_value ( realm , * data [ index ] ) ;
// 2. Let status be CreateDataProperty(array, index, entry).
auto status = MUST ( array - > create_data_property ( index , entry ) ) ;
// 3. Assert: status is true.
VERIFY ( status ) ;
// 4. Increase index by 1.
index + + ;
}
// 6. Return array.
return array ;
}
2025-04-25 17:59:06 +02:00
case Key : : KeyType : : Invalid :
VERIFY_NOT_REACHED ( ) ;
2025-01-08 23:25:46 +01:00
}
VERIFY_NOT_REACHED ( ) ;
}
2025-03-24 19:34:05 +01:00
// https://w3c.github.io/IndexedDB/#valid-key-path
bool is_valid_key_path ( KeyPath const & path )
{
// A valid key path is one of:
return path . visit (
[ ] ( String const & value ) - > bool {
// * An empty string.
if ( value . is_empty ( ) )
return true ;
// FIXME: * An identifier, which is a string matching the IdentifierName production from the ECMAScript Language Specification [ECMA-262].
return true ;
// FIXME: * A string consisting of two or more identifiers separated by periods (U+002E FULL STOP).
return true ;
return false ;
} ,
[ ] ( Vector < String > const & values ) - > bool {
// * A non-empty list containing only strings conforming to the above requirements.
if ( values . is_empty ( ) )
return false ;
for ( auto const & value : values ) {
if ( ! is_valid_key_path ( value ) )
return false ;
}
return true ;
} ) ;
}
2025-03-24 21:23:58 +01:00
// https://w3c.github.io/IndexedDB/#create-a-sorted-name-list
GC : : Ref < HTML : : DOMStringList > create_a_sorted_name_list ( JS : : Realm & realm , Vector < String > names )
{
// 1. Let sorted be names sorted in ascending order with the code unit less than algorithm.
quick_sort ( names , [ ] ( auto const & a , auto const & b ) {
return Infra : : code_unit_less_than ( a , b ) ;
} ) ;
// 2. Return a new DOMStringList associated with sorted.
return HTML : : DOMStringList : : create ( realm , names ) ;
}
2025-04-09 23:11:12 +02:00
// https://w3c.github.io/IndexedDB/#commit-a-transaction
void commit_a_transaction ( JS : : Realm & realm , GC : : Ref < IDBTransaction > transaction )
{
// 1. Set transaction’ s state to committing.
transaction - > set_state ( IDBTransaction : : TransactionState : : Committing ) ;
dbgln_if ( IDB_DEBUG , " commit_a_transaction: transaction {} is committing " , transaction - > uuid ( ) ) ;
// 2. Run the following steps in parallel:
Platform : : EventLoopPlugin : : the ( ) . deferred_invoke ( GC : : create_function ( realm . heap ( ) , [ & realm , transaction ] ( ) {
HTML : : TemporaryExecutionContext context ( realm , HTML : : TemporaryExecutionContext : : CallbacksEnabled : : Yes ) ;
// 1. Wait until every item in transaction’ s request list is processed.
HTML : : main_thread_event_loop ( ) . spin_until ( GC : : create_function ( realm . vm ( ) . heap ( ) , [ transaction ] ( ) {
if constexpr ( IDB_DEBUG ) {
dbgln ( " commit_a_transaction: waiting for step 1 " ) ;
dbgln ( " requests in queue: " ) ;
for ( auto const & request : transaction - > request_list ( ) ) {
dbgln ( " - {} = {} " , request - > uuid ( ) , request - > processed ( ) ? " processed " sv : " not processed " sv ) ;
}
}
return transaction - > request_list ( ) . all_requests_processed ( ) ;
} ) ) ;
// 2. If transaction’ s state is no longer committing, then terminate these steps.
if ( transaction - > state ( ) ! = IDBTransaction : : TransactionState : : Committing )
return ;
// FIXME: 3. Attempt to write any outstanding changes made by transaction to the database, considering transaction’ s durability hint.
// FIXME: 4. If an error occurs while writing the changes to the database, then run abort a transaction with transaction and an appropriate type for the error, for example "QuotaExceededError" or "UnknownError" DOMException, and terminate these steps.
2025-05-16 08:26:03 +02:00
// 5. Queue a database task to run these steps:
queue_a_database_task ( GC : : create_function ( transaction - > realm ( ) . vm ( ) . heap ( ) , [ transaction ] ( ) {
2025-04-09 23:11:12 +02:00
// 1. If transaction is an upgrade transaction, then set transaction’ s connection's associated database's upgrade transaction to null.
if ( transaction - > is_upgrade_transaction ( ) )
transaction - > connection ( ) - > associated_database ( ) - > set_upgrade_transaction ( nullptr ) ;
// 2. Set transaction’ s state to finished.
transaction - > set_state ( IDBTransaction : : TransactionState : : Finished ) ;
// 3. Fire an event named complete at transaction.
transaction - > dispatch_event ( DOM : : Event : : create ( transaction - > realm ( ) , HTML : : EventNames : : complete ) ) ;
// 4. If transaction is an upgrade transaction, then let request be the request associated with transaction and set request’ s transaction to null.
if ( transaction - > is_upgrade_transaction ( ) ) {
auto request = transaction - > associated_request ( ) ;
request - > set_transaction ( nullptr ) ;
// Ad-hoc: Clear the two-way binding.
transaction - > set_associated_request ( nullptr ) ;
}
} ) ) ;
} ) ) ;
}
2025-04-11 10:38:55 +02:00
// https://w3c.github.io/IndexedDB/#clone
WebIDL : : ExceptionOr < JS : : Value > clone_in_realm ( JS : : Realm & target_realm , JS : : Value value , GC : : Ref < IDBTransaction > transaction )
{
auto & vm = target_realm . vm ( ) ;
// 1. Assert: transaction’ s state is active.
VERIFY ( transaction - > state ( ) = = IDBTransaction : : TransactionState : : Active ) ;
// 2. Set transaction’ s state to inactive.
transaction - > set_state ( IDBTransaction : : TransactionState : : Inactive ) ;
// 3. Let serialized be ? StructuredSerializeForStorage(value).
auto serialized = TRY ( HTML : : structured_serialize_for_storage ( vm , value ) ) ;
// 4. Let clone be ? StructuredDeserialize(serialized, targetRealm).
auto clone = TRY ( HTML : : structured_deserialize ( vm , serialized , target_realm ) ) ;
// 5. Set transaction’ s state to active.
transaction - > set_state ( IDBTransaction : : TransactionState : : Active ) ;
// 6. Return clone.
return clone ;
}
2025-04-11 10:46:21 +02:00
// https://w3c.github.io/IndexedDB/#convert-a-value-to-a-multientry-key
2025-04-25 17:59:06 +02:00
WebIDL : : ExceptionOr < GC : : Ref < Key > > convert_a_value_to_a_multi_entry_key ( JS : : Realm & realm , JS : : Value value )
2025-04-11 10:46:21 +02:00
{
// 1. If input is an Array exotic object, then:
if ( value . is_object ( ) & & is < JS : : Array > ( value . as_object ( ) ) ) {
// 1. Let len be ? ToLength( ? Get(input, "length")).
auto len = TRY ( length_of_array_like ( realm . vm ( ) , value . as_object ( ) ) ) ;
// 2. Let seen be a new set containing only input.
Vector < JS : : Value > seen { value } ;
// 3. Let keys be a new empty list.
Vector < GC : : Root < Key > > keys ;
// 4. Let index be 0.
u64 index = 0 ;
// 5. While index is less than len:
while ( index < len ) {
// 1. Let entry be Get(input, index).
auto maybe_entry = value . as_object ( ) . get ( index ) ;
// 2. If entry is not an abrupt completion, then:
if ( ! maybe_entry . is_error ( ) ) {
// 1. Let key be the result of converting a value to a key with arguments entry and seen.
auto completion_key = convert_a_value_to_a_key ( realm , maybe_entry . release_value ( ) , seen ) ;
// 2. If key is not invalid or an abrupt completion, and there is no item in keys equal to key, then append key to keys.
if ( ! completion_key . is_error ( ) ) {
2025-04-25 17:59:06 +02:00
auto key = completion_key . release_value ( ) ;
2025-04-11 10:46:21 +02:00
2025-04-25 17:59:06 +02:00
if ( ! key - > is_invalid ( ) & & ! keys . contains_slow ( key ) )
keys . append ( key ) ;
2025-04-11 10:46:21 +02:00
}
}
// 3. Increase index by 1.
index + + ;
}
// 6. Return a new array key with value set to keys.
return Key : : create_array ( realm , keys ) ;
}
// 2. Otherwise, return the result of converting a value to a key with argument input. Rethrow any exceptions.
return convert_a_value_to_a_key ( realm , value ) ;
}
2025-04-11 10:51:03 +02:00
// https://w3c.github.io/IndexedDB/#evaluate-a-key-path-on-a-value
WebIDL : : ExceptionOr < ErrorOr < JS : : Value > > evaluate_key_path_on_a_value ( JS : : Realm & realm , JS : : Value value , KeyPath const & key_path )
{
// 1. If keyPath is a list of strings, then:
if ( key_path . has < Vector < String > > ( ) ) {
auto const & key_path_list = key_path . get < Vector < String > > ( ) ;
// 1. Let result be a new Array object created as if by the expression [].
auto result = MUST ( JS : : Array : : create ( realm , 0 ) ) ;
// 2. Let i be 0.
u64 i = 0 ;
// 3. For each item of keyPath:
for ( auto const & item : key_path_list ) {
// 1. Let key be the result of recursively evaluating a key path on a value with item and value.
auto completion_key = evaluate_key_path_on_a_value ( realm , value , item ) ;
// 2. Assert: key is not an abrupt completion.
VERIFY ( ! completion_key . is_error ( ) ) ;
// 3. If key is failure, abort the overall algorithm and return failure.
auto key = TRY ( TRY ( completion_key ) ) ;
// 4. Let p be ! ToString(i).
auto p = JS : : PropertyKey { i } ;
// 5. Let status be CreateDataProperty(result, p, key).
auto status = MUST ( result - > create_data_property ( p , key ) ) ;
// 6. Assert: status is true.
VERIFY ( status ) ;
// 7. Increase i by 1.
i + + ;
}
// 4. Return result.
return result ;
}
auto const & key_path_string = key_path . get < String > ( ) ;
// 2. If keyPath is the empty string, return value and skip the remaining steps.
if ( key_path_string . is_empty ( ) )
return value ;
// 3. Let identifiers be the result of strictly splitting keyPath on U+002E FULL STOP characters (.).
// 4. For each identifier of identifiers, jump to the appropriate step below:
2025-04-25 18:19:05 +02:00
TRY ( key_path_string . bytes_as_string_view ( ) . for_each_split_view ( ' . ' , SplitBehavior : : KeepEmpty , [ & ] ( auto const & identifier ) - > ErrorOr < void > {
2025-04-11 10:51:03 +02:00
// If Type(value) is String, and identifier is "length"
if ( value . is_string ( ) & & identifier = = " length " ) {
// Let value be a Number equal to the number of elements in value.
2025-05-04 00:06:34 +12:00
value = JS : : Value ( value . as_string ( ) . length_in_utf16_code_units ( ) ) ;
2025-04-11 10:51:03 +02:00
}
// If value is an Array and identifier is "length"
else if ( value . is_object ( ) & & is < JS : : Array > ( value . as_object ( ) ) & & identifier = = " length " ) {
// Let value be ! ToLength(! Get(value, "length")).
value = JS : : Value ( MUST ( length_of_array_like ( realm . vm ( ) , value . as_object ( ) ) ) ) ;
}
// If value is a Blob and identifier is "size"
else if ( value . is_object ( ) & & is < FileAPI : : Blob > ( value . as_object ( ) ) & & identifier = = " size " ) {
// Let value be value’ s size.
value = JS : : Value ( static_cast < FileAPI : : Blob & > ( value . as_object ( ) ) . size ( ) ) ;
}
// If value is a Blob and identifier is "type"
else if ( value . is_object ( ) & & is < FileAPI : : Blob > ( value . as_object ( ) ) & & identifier = = " type " ) {
// Let value be a String equal to value’ s type.
value = JS : : PrimitiveString : : create ( realm . vm ( ) , static_cast < FileAPI : : Blob & > ( value . as_object ( ) ) . type ( ) ) ;
}
// If value is a File and identifier is "name"
else if ( value . is_object ( ) & & is < FileAPI : : File > ( value . as_object ( ) ) & & identifier = = " name " ) {
// Let value be a String equal to value’ s name.
value = JS : : PrimitiveString : : create ( realm . vm ( ) , static_cast < FileAPI : : File & > ( value . as_object ( ) ) . name ( ) ) ;
}
// If value is a File and identifier is "lastModified"
else if ( value . is_object ( ) & & is < FileAPI : : File > ( value . as_object ( ) ) & & identifier = = " lastModified " ) {
// Let value be a Number equal to value’ s lastModified.
value = JS : : Value ( static_cast < double > ( static_cast < FileAPI : : File & > ( value . as_object ( ) ) . last_modified ( ) ) ) ;
}
// Otherwise
else {
// 1. If Type(value) is not Object, return failure.
if ( ! value . is_object ( ) )
2025-05-08 09:48:06 +02:00
return Error : : from_string_literal ( " Value is not an object during key path evaluation " ) ;
2025-04-11 10:51:03 +02:00
auto identifier_property = String : : from_utf8_without_validation ( identifier . bytes ( ) ) ;
// 2. Let hop be ! HasOwnProperty(value, identifier).
auto hop = MUST ( value . as_object ( ) . has_own_property ( identifier_property ) ) ;
// 3. If hop is false, return failure.
if ( ! hop )
2025-05-08 09:48:06 +02:00
return Error : : from_string_literal ( " Failed to find property on object during key path evaluation " ) ;
2025-04-11 10:51:03 +02:00
// 4. Let value be ! Get(value, identifier).
value = MUST ( value . as_object ( ) . get ( identifier_property ) ) ;
// 5. If value is undefined, return failure.
if ( value . is_undefined ( ) )
2025-05-08 09:48:06 +02:00
return Error : : from_string_literal ( " undefined value on object during key path evaluation " ) ;
2025-04-11 10:51:03 +02:00
}
return { } ;
} ) ) ;
// 5. Assert: value is not an abrupt completion.
// NOTE: Step 4 above makes this assertion via MUST
// 6. Return value.
return value ;
}
2025-04-11 10:47:08 +02:00
// https://w3c.github.io/IndexedDB/#extract-a-key-from-a-value-using-a-key-path
WebIDL : : ExceptionOr < ErrorOr < GC : : Ref < Key > > > extract_a_key_from_a_value_using_a_key_path ( JS : : Realm & realm , JS : : Value value , KeyPath const & key_path , bool multi_entry )
{
// 1. Let r be the result of evaluating a key path on a value with value and keyPath. Rethrow any exceptions.
// 2. If r is failure, return failure.
auto r = TRY ( TRY ( evaluate_key_path_on_a_value ( realm , value , key_path ) ) ) ;
// 3. Let key be the result of converting a value to a key with r if the multiEntry flag is false,
// and the result of converting a value to a multiEntry key with r otherwise. Rethrow any exceptions.
// 4. If key is invalid, return invalid.
// 5. Return key.
return multi_entry ? TRY ( convert_a_value_to_a_multi_entry_key ( realm , r ) ) : TRY ( convert_a_value_to_a_key ( realm , r ) ) ;
}
2025-04-11 10:54:23 +02:00
// https://w3c.github.io/IndexedDB/#check-that-a-key-could-be-injected-into-a-value
bool check_that_a_key_could_be_injected_into_a_value ( JS : : Realm & realm , JS : : Value value , KeyPath const & key_path )
{
// NOTE: The key paths used in this section are always strings and never sequences
// 1. Let identifiers be the result of strictly splitting keyPath on U+002E FULL STOP characters (.).
auto identifiers = MUST ( key_path . get < String > ( ) . split ( ' . ' ) ) ;
// 2. Assert: identifiers is not empty.
VERIFY ( ! identifiers . is_empty ( ) ) ;
// 3. Remove the last item of identifiers.
identifiers . take_last ( ) ;
// 4. For each remaining identifier of identifiers, if any:
for ( auto const & identifier : identifiers ) {
// 1. If value is not an Object or an Array, return false.
if ( ! ( value . is_object ( ) | | MUST ( value . is_array ( realm . vm ( ) ) ) ) )
return false ;
// 2. Let hop be ! HasOwnProperty(value, identifier).
auto hop = MUST ( value . as_object ( ) . has_own_property ( identifier ) ) ;
// 3. If hop is false, return true.
if ( ! hop )
return true ;
// 4. Let value be ! Get(value, identifier).
value = MUST ( value . as_object ( ) . get ( identifier ) ) ;
}
// 5. Return true if value is an Object or an Array, or false otherwise.
return value . is_object ( ) | | MUST ( value . is_array ( realm . vm ( ) ) ) ;
}
2025-04-11 11:09:16 +02:00
// https://w3c.github.io/IndexedDB/#fire-an-error-event
void fire_an_error_event ( JS : : Realm & realm , GC : : Ref < IDBRequest > request )
{
// 1. Let event be the result of creating an event using Event.
// 2. Set event’ s type attribute to "error".
// 3. Set event’ s bubbles and cancelable attributes to true.
auto event = DOM : : Event : : create ( realm , HTML : : EventNames : : error , { . bubbles = true , . cancelable = true } ) ;
// 4. Let transaction be request’ s transaction.
auto transaction = request - > transaction ( ) ;
// 5. Let legacyOutputDidListenersThrowFlag be initially false.
bool legacy_output_did_listeners_throw_flag = false ;
// 6. If transaction’ s state is inactive, then set transaction’ s state to active.
if ( transaction - > state ( ) = = IDBTransaction : : TransactionState : : Inactive )
transaction - > set_state ( IDBTransaction : : TransactionState : : Active ) ;
// 7. Dispatch event at request with legacyOutputDidListenersThrowFlag.
DOM : : EventDispatcher : : dispatch ( request , * event , false , legacy_output_did_listeners_throw_flag ) ;
// 8. If transaction’ s state is active, then:
if ( transaction - > state ( ) = = IDBTransaction : : TransactionState : : Active ) {
// 1. Set transaction’ s state to inactive.
transaction - > set_state ( IDBTransaction : : TransactionState : : Inactive ) ;
// 2. If legacyOutputDidListenersThrowFlag is true, then run abort a transaction with transaction and a newly created "AbortError" DOMException and terminate these steps.
// This is done even if event’ s canceled flag is false.
if ( legacy_output_did_listeners_throw_flag ) {
abort_a_transaction ( * transaction , WebIDL : : AbortError : : create ( realm , " Error event interrupted by exception " _string ) ) ;
return ;
}
// 3. If event’ s canceled flag is false, then run abort a transaction using transaction and request's error, and terminate these steps.
if ( ! event - > cancelled ( ) ) {
abort_a_transaction ( * transaction , request - > error ( ) ) ;
return ;
}
// 4. If transaction’ s request list is empty, then run commit a transaction with transaction.
if ( transaction - > request_list ( ) . is_empty ( ) )
commit_a_transaction ( realm , * transaction ) ;
}
}
2025-04-11 11:10:05 +02:00
// https://w3c.github.io/IndexedDB/#fire-a-success-event
void fire_a_success_event ( JS : : Realm & realm , GC : : Ref < IDBRequest > request )
{
// 1. Let event be the result of creating an event using Event.
// 2. Set event’ s type attribute to "success".
// 3. Set event’ s bubbles and cancelable attributes to false.
auto event = DOM : : Event : : create ( realm , HTML : : EventNames : : success , { . bubbles = false , . cancelable = false } ) ;
// 4. Let transaction be request’ s transaction.
auto transaction = request - > transaction ( ) ;
// 5. Let legacyOutputDidListenersThrowFlag be initially false.
bool legacy_output_did_listeners_throw_flag = false ;
// 6. If transaction’ s state is inactive, then set transaction’ s state to active.
if ( transaction - > state ( ) = = IDBTransaction : : TransactionState : : Inactive )
transaction - > set_state ( IDBTransaction : : TransactionState : : Active ) ;
// 7. Dispatch event at request with legacyOutputDidListenersThrowFlag.
DOM : : EventDispatcher : : dispatch ( request , * event , false , legacy_output_did_listeners_throw_flag ) ;
// 8. If transaction’ s state is active, then:
if ( transaction - > state ( ) = = IDBTransaction : : TransactionState : : Active ) {
// 1. Set transaction’ s state to inactive.
transaction - > set_state ( IDBTransaction : : TransactionState : : Inactive ) ;
// 2. If legacyOutputDidListenersThrowFlag is true, then run abort a transaction with transaction and a newly created "AbortError" DOMException.
if ( legacy_output_did_listeners_throw_flag ) {
abort_a_transaction ( * transaction , WebIDL : : AbortError : : create ( realm , " An error occurred " _string ) ) ;
return ;
}
// 3. If transaction’ s request list is empty, then run commit a transaction with transaction.
if ( transaction - > request_list ( ) . is_empty ( ) )
commit_a_transaction ( realm , * transaction ) ;
}
}
2025-04-11 11:22:29 +02:00
// https://w3c.github.io/IndexedDB/#asynchronously-execute-a-request
GC : : Ref < IDBRequest > asynchronously_execute_a_request ( JS : : Realm & realm , IDBRequestSource source , GC : : Ref < GC : : Function < WebIDL : : ExceptionOr < JS : : Value > ( ) > > operation , GC : : Ptr < IDBRequest > request_input )
{
// 1. Let transaction be the transaction associated with source.
auto transaction = source . visit (
[ ] ( Empty ) - > GC : : Ptr < IDBTransaction > {
VERIFY_NOT_REACHED ( ) ;
} ,
[ ] ( GC : : Ref < IDBObjectStore > object_store ) - > GC : : Ptr < IDBTransaction > {
return object_store - > transaction ( ) ;
} ,
[ ] ( GC : : Ref < IDBIndex > index ) - > GC : : Ptr < IDBTransaction > {
return index - > transaction ( ) ;
} ,
[ ] ( GC : : Ref < IDBCursor > cursor ) - > GC : : Ptr < IDBTransaction > {
return cursor - > transaction ( ) ;
} ) ;
// 2. Assert: transaction’ s state is active.
VERIFY ( transaction - > state ( ) = = IDBTransaction : : TransactionState : : Active ) ;
// 3. If request was not given, let request be a new request with source as source.
GC : : Ref < IDBRequest > request = request_input ? GC : : Ref ( * request_input ) : IDBRequest : : create ( realm , source ) ;
// 4. Add request to the end of transaction’ s request list.
transaction - > request_list ( ) . append ( request ) ;
// Set the two-way binding. (Missing spec step)
// FIXME: https://github.com/w3c/IndexedDB/issues/433
request - > set_transaction ( transaction ) ;
// 5. Run these steps in parallel:
Platform : : EventLoopPlugin : : the ( ) . deferred_invoke ( GC : : create_function ( realm . heap ( ) , [ & realm , transaction , operation , request ] ( ) {
HTML : : TemporaryExecutionContext context ( realm , HTML : : TemporaryExecutionContext : : CallbacksEnabled : : Yes ) ;
// 1. Wait until request is the first item in transaction’ s request list that is not processed.
HTML : : main_thread_event_loop ( ) . spin_until ( GC : : create_function ( realm . vm ( ) . heap ( ) , [ transaction , request ] ( ) {
if constexpr ( IDB_DEBUG ) {
dbgln ( " asynchronously_execute_a_request: waiting for step 5.1 " ) ;
dbgln ( " requests in queue: " ) ;
for ( auto const & item : transaction - > request_list ( ) ) {
dbgln ( " [{}] - {} = {} " , item = = request ? " x " sv : " " sv , item - > uuid ( ) , item - > processed ( ) ? " processed " sv : " not processed " sv ) ;
}
}
return transaction - > request_list ( ) . all_previous_requests_processed ( request ) ;
} ) ) ;
// 2. Let result be the result of performing operation.
auto result = operation - > function ( ) ( ) ;
// 3. If result is an error and transaction’ s state is committing, then run abort a transaction with transaction and result, and terminate these steps.
if ( result . is_error ( ) & & transaction - > state ( ) = = IDBTransaction : : TransactionState : : Committing ) {
abort_a_transaction ( * transaction , result . exception ( ) . get < GC : : Ref < WebIDL : : DOMException > > ( ) ) ;
return ;
}
// FIXME: 4. If result is an error, then revert all changes made by operation.
// 5. Set request’ s processed flag to true.
request - > set_processed ( true ) ;
2025-05-16 08:26:03 +02:00
// 6. Queue a database task to run these steps:
queue_a_database_task ( GC : : create_function ( realm . vm ( ) . heap ( ) , [ & realm , request , result , transaction ] ( ) mutable {
2025-04-11 11:22:29 +02:00
// 1. Remove request from transaction’ s request list.
transaction - > request_list ( ) . remove_first_matching ( [ & request ] ( auto & entry ) { return entry . ptr ( ) = = request . ptr ( ) ; } ) ;
// 2. Set request’ s done flag to true.
request - > set_done ( true ) ;
// 3. 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. Fire an error event at request.
fire_an_error_event ( realm , request ) ;
} else {
// 1. Set request’ s result to result.
request - > set_result ( result . release_value ( ) ) ;
// 2. Set request’ s error to undefined.
request - > set_error ( nullptr ) ;
// 3. Fire a success event at request.
fire_a_success_event ( realm , request ) ;
}
} ) ) ;
} ) ) ;
// 6. Return request.
return request ;
}
2025-04-11 11:25:56 +02:00
// https://w3c.github.io/IndexedDB/#generate-a-key
ErrorOr < u64 > generate_a_key ( GC : : Ref < ObjectStore > store )
{
// 1. Let generator be store’ s key generator.
2025-04-25 18:12:28 +02:00
auto & generator = store - > key_generator ( ) ;
2025-04-11 11:25:56 +02:00
// 2. Let key be generator’ s current number.
auto key = generator . current_number ( ) ;
// 3. If key is greater than 2^53 (9007199254740992), then return failure.
if ( key > static_cast < u64 > ( MAX_KEY_GENERATOR_VALUE ) )
2025-05-08 09:48:06 +02:00
return Error : : from_string_literal ( " Key is greater than 2^53 while trying to generate a key " ) ;
2025-04-11 11:25:56 +02:00
// 4. Increase generator’ s current number by 1.
generator . increment ( 1 ) ;
// 5. Return key.
return key ;
}
2025-04-11 11:27:22 +02:00
// https://w3c.github.io/IndexedDB/#possibly-update-the-key-generator
void possibly_update_the_key_generator ( GC : : Ref < ObjectStore > store , GC : : Ref < Key > key )
{
// 1. If the type of key is not number, abort these steps.
if ( key - > type ( ) ! = Key : : KeyType : : Number )
return ;
// 2. Let value be the value of key.
auto temp_value = key - > value_as_double ( ) ;
// 3. Set value to the minimum of value and 2^53 (9007199254740992).
temp_value = min ( temp_value , MAX_KEY_GENERATOR_VALUE ) ;
// 4. Set value to the largest integer not greater than value.
u64 value = floor ( temp_value ) ;
// 5. Let generator be store’ s key generator.
2025-04-25 18:12:28 +02:00
auto & generator = store - > key_generator ( ) ;
2025-04-11 11:27:22 +02:00
// 6. If value is greater than or equal to generator’ s current number, then set generator’ s current number to value + 1.
if ( value > = generator . current_number ( ) )
generator . set ( value + 1 ) ;
}
2025-04-11 11:28:11 +02:00
// https://w3c.github.io/IndexedDB/#inject-a-key-into-a-value-using-a-key-path
void inject_a_key_into_a_value_using_a_key_path ( JS : : Realm & realm , JS : : Value value , GC : : Ref < Key > key , KeyPath const & key_path )
{
// 1. Let identifiers be the result of strictly splitting keyPath on U+002E FULL STOP characters (.).
auto identifiers = MUST ( key_path . get < String > ( ) . split ( ' . ' ) ) ;
// 2. Assert: identifiers is not empty.
VERIFY ( ! identifiers . is_empty ( ) ) ;
// 3. Let last be the last item of identifiers and remove it from the list.
auto last = identifiers . take_last ( ) ;
// 4. For each remaining identifier of identifiers:
for ( auto const & identifier : identifiers ) {
// 1. Assert: value is an Object or an Array.
VERIFY ( value . is_object ( ) | | MUST ( value . is_array ( realm . vm ( ) ) ) ) ;
// 2. Let hop be ! HasOwnProperty(value, identifier).
auto hop = MUST ( value . as_object ( ) . has_own_property ( identifier ) ) ;
// 3. If hop is false, then:
if ( ! hop ) {
// 1. Let o be a new Object created as if by the expression ({}).
auto o = JS : : Object : : create ( realm , realm . intrinsics ( ) . object_prototype ( ) ) ;
// 2. Let status be CreateDataProperty(value, identifier, o).
auto status = MUST ( value . as_object ( ) . create_data_property ( identifier , o ) ) ;
// 3. Assert: status is true.
VERIFY ( status ) ;
}
// 4. Let value be ! Get(value, identifier).
value = MUST ( value . as_object ( ) . get ( identifier ) ) ;
}
// 5. Assert: value is an Object or an Array.
VERIFY ( value . is_object ( ) | | MUST ( value . is_array ( realm . vm ( ) ) ) ) ;
// 6. Let keyValue be the result of converting a key to a value with key.
auto key_value = convert_a_key_to_a_value ( realm , key ) ;
// 7. Let status be CreateDataProperty(value, last, keyValue).
auto status = MUST ( value . as_object ( ) . create_data_property ( last , key_value ) ) ;
// 8. Assert: status is true.
VERIFY ( status ) ;
}
2025-04-11 11:34:52 +02:00
// https://w3c.github.io/IndexedDB/#delete-records-from-an-object-store
2025-05-08 09:59:13 +02:00
JS : : Value delete_records_from_an_object_store ( GC : : Ref < ObjectStore > store , GC : : Ref < IDBKeyRange > range )
2025-04-11 11:34:52 +02:00
{
// 1. Remove all records, if any, from store’ s list of records with key in range.
store - > remove_records_in_range ( range ) ;
2025-05-13 23:09:28 +02:00
// 2. For each index which references store, remove every record from index’ s list of records whose value is in range, if any such records exist.
for ( auto const & [ name , index ] : store - > index_set ( ) ) {
index - > remove_records_with_value_in_range ( range ) ;
}
2025-04-11 11:34:52 +02:00
// 3. Return undefined.
2025-05-08 09:59:13 +02:00
return JS : : js_undefined ( ) ;
2025-04-11 11:34:52 +02:00
}
2025-04-11 11:38:29 +02:00
// https://w3c.github.io/IndexedDB/#store-a-record-into-an-object-store
WebIDL : : ExceptionOr < GC : : Ptr < Key > > store_a_record_into_an_object_store ( JS : : Realm & realm , GC : : Ref < ObjectStore > store , JS : : Value value , GC : : Ptr < Key > key , bool no_overwrite )
{
// 1. If store uses a key generator, then:
2025-04-25 18:12:28 +02:00
if ( store - > uses_a_key_generator ( ) ) {
2025-04-11 11:38:29 +02:00
// 1. If key is undefined, then:
if ( key = = nullptr ) {
// 1. Let key be the result of generating a key for store.
auto maybe_key = generate_a_key ( store ) ;
// 2. If key is failure, then this operation failed with a "ConstraintError" DOMException. Abort this algorithm without taking any further steps.
if ( maybe_key . is_error ( ) )
return WebIDL : : ConstraintError : : create ( realm , String : : from_utf8_without_validation ( maybe_key . error ( ) . string_literal ( ) . bytes ( ) ) ) ;
key = Key : : create_number ( realm , static_cast < double > ( maybe_key . value ( ) ) ) ;
// 3. If store also uses in-line keys, then run inject a key into a value using a key path with value, key and store’ s key path.
if ( store - > uses_inline_keys ( ) )
inject_a_key_into_a_value_using_a_key_path ( realm , value , GC : : Ref ( * key ) , store - > key_path ( ) . value ( ) ) ;
}
// 2. Otherwise, run possibly update the key generator for store with key.
else {
possibly_update_the_key_generator ( store , GC : : Ref ( * key ) ) ;
}
}
// 2. If the no-overwrite flag was given to these steps and is true, and a record already exists in store with its key equal to key,
// then this operation failed with a "ConstraintError" DOMException. Abort this algorithm without taking any further steps.
auto has_record = store - > has_record_with_key ( * key ) ;
if ( no_overwrite & & has_record )
return WebIDL : : ConstraintError : : create ( realm , " Record already exists " _string ) ;
// 3. If a record already exists in store with its key equal to key, then remove the record from store using delete records from an object store.
if ( has_record ) {
2025-05-13 09:16:06 +02:00
auto key_range = IDBKeyRange : : create ( realm , key , key , IDBKeyRange : : LowerOpen : : No , IDBKeyRange : : UpperOpen : : No ) ;
2025-04-11 11:38:29 +02:00
delete_records_from_an_object_store ( store , key_range ) ;
}
// 4. Store a record in store containing key as its key and ! StructuredSerializeForStorage(value) as its value.
// The record is stored in the object store’ s list of records such that the list is sorted according to the key of the records in ascending order.
Record record = {
. key = * key ,
. value = MUST ( HTML : : structured_serialize_for_storage ( realm . vm ( ) , value ) ) ,
} ;
store - > store_a_record ( record ) ;
// 5. For each index which references store:
for ( auto const & [ name , index ] : store - > index_set ( ) ) {
// 1. Let index key be the result of extracting a key from a value using a key path with value, index’ s key path, and index’ s multiEntry flag.
2025-04-25 17:59:06 +02:00
auto completion_index_key = extract_a_key_from_a_value_using_a_key_path ( realm , value , index - > key_path ( ) , index - > multi_entry ( ) ) ;
2025-04-11 11:38:29 +02:00
// 2. If index key is an exception, or invalid, or failure, take no further actions for index, and continue these steps for the next index.
2025-04-25 17:59:06 +02:00
if ( completion_index_key . is_error ( ) )
continue ;
auto failure_index_key = completion_index_key . release_value ( ) ;
if ( failure_index_key . is_error ( ) )
continue ;
auto index_key = failure_index_key . release_value ( ) ;
if ( index_key - > is_invalid ( ) )
2025-04-11 11:38:29 +02:00
continue ;
auto index_multi_entry = index - > multi_entry ( ) ;
2025-04-25 17:59:06 +02:00
auto index_key_is_array = index_key - > type ( ) = = Key : : KeyType : : Array ;
2025-04-11 11:38:29 +02:00
auto index_is_unique = index - > unique ( ) ;
// 3. If index’ s multiEntry flag is false, or if index key is not an array key,
// and if index already contains a record with key equal to index key,
// and index’ s unique flag is true,
// then this operation failed with a "ConstraintError" DOMException.
// Abort this algorithm without taking any further steps.
2025-04-25 17:59:06 +02:00
if ( ( ! index_multi_entry | | ! index_key_is_array ) & & index_is_unique & & index - > has_record_with_key ( index_key ) )
2025-04-11 11:38:29 +02:00
return WebIDL : : ConstraintError : : create ( realm , " Record already exists in index " _string ) ;
// 4. If index’ s multiEntry flag is true and index key is an array key,
// and if index already contains a record with key equal to any of the subkeys of index key,
// and index’ s unique flag is true,
// then this operation failed with a "ConstraintError" DOMException.
// Abort this algorithm without taking any further steps.
if ( index_multi_entry & & index_key_is_array & & index_is_unique ) {
2025-04-25 17:59:06 +02:00
for ( auto const & subkey : index_key - > subkeys ( ) ) {
2025-04-11 11:38:29 +02:00
if ( index - > has_record_with_key ( * subkey ) )
return WebIDL : : ConstraintError : : create ( realm , " Record already exists in index " _string ) ;
}
}
2025-05-13 23:06:42 +02:00
// 5. If index’ s multiEntry flag is false, or if index key is not an array key
2025-04-11 11:38:29 +02:00
// then store a record in index containing index key as its key and key as its value.
// The record is stored in index’ s list of records such that the list is sorted primarily on the records keys,
// and secondarily on the records values, in ascending order.
2025-05-13 23:06:42 +02:00
if ( ! index_multi_entry | | ! index_key_is_array ) {
IndexRecord index_record = {
. key = * index_key ,
. value = * key ,
} ;
index - > store_a_record ( index_record ) ;
}
2025-04-11 11:38:29 +02:00
2025-05-13 23:06:42 +02:00
// 6. If index’ s multiEntry flag is true and index key is an array key,
2025-04-11 11:38:29 +02:00
// then for each subkey of the subkeys of index key store a record in index containing subkey as its key and key as its value.
2025-05-13 23:06:42 +02:00
if ( index_multi_entry & & index_key_is_array ) {
for ( auto const & subkey : index_key - > subkeys ( ) ) {
IndexRecord index_record = {
. key = * subkey ,
. value = * key ,
} ;
index - > store_a_record ( index_record ) ;
}
}
2025-04-11 11:38:29 +02:00
}
// 6. Return key.
return key ;
}
2025-04-28 15:49:38 +02:00
// https://w3c.github.io/IndexedDB/#convert-a-value-to-a-key-range
WebIDL : : ExceptionOr < GC : : Ref < IDBKeyRange > > convert_a_value_to_a_key_range ( JS : : Realm & realm , Optional < JS : : Value > value , bool null_disallowed )
{
// 1. If value is a key range, return value.
if ( value . has_value ( ) & & value - > is_object ( ) & & is < IDBKeyRange > ( value - > as_object ( ) ) ) {
return GC : : Ref ( static_cast < IDBKeyRange & > ( value - > as_object ( ) ) ) ;
}
// 2. If value is undefined or is null, then throw a "DataError" DOMException if null disallowed flag is true, or return an unbounded key range otherwise.
if ( ! value . has_value ( ) | | ( value . has_value ( ) & & ( value - > is_undefined ( ) | | value - > is_null ( ) ) ) ) {
if ( null_disallowed )
return WebIDL : : DataError : : create ( realm , " Value is undefined or null " _string ) ;
2025-05-13 09:16:06 +02:00
return IDBKeyRange : : create ( realm , { } , { } , IDBKeyRange : : LowerOpen : : No , IDBKeyRange : : UpperOpen : : No ) ;
2025-04-28 15:49:38 +02:00
}
// 3. Let key be the result of converting a value to a key with value. Rethrow any exceptions.
auto key = TRY ( convert_a_value_to_a_key ( realm , * value ) ) ;
// 4. If key is invalid, throw a "DataError" DOMException.
if ( key - > is_invalid ( ) )
return WebIDL : : DataError : : create ( realm , " Value is invalid " _string ) ;
// 5. Return a key range containing only key.
2025-05-13 09:16:06 +02:00
return IDBKeyRange : : create ( realm , key , key , IDBKeyRange : : LowerOpen : : No , IDBKeyRange : : UpperOpen : : No ) ;
2025-04-28 15:49:38 +02:00
}
2025-04-28 15:51:35 +02:00
// https://w3c.github.io/IndexedDB/#count-the-records-in-a-range
2025-05-13 22:54:08 +02:00
JS : : Value count_the_records_in_a_range ( RecordSource source , GC : : Ref < IDBKeyRange > range )
2025-04-28 15:51:35 +02:00
{
// 1. Let count be the number of records, if any, in source’ s list of records with key in range.
2025-05-13 22:54:08 +02:00
auto count = source . visit (
[ range ] ( GC : : Ref < ObjectStore > object_store ) {
return object_store - > count_records_in_range ( range ) ;
} ,
[ range ] ( GC : : Ref < Index > index ) {
return index - > count_records_in_range ( range ) ;
} ) ;
2025-04-28 15:51:35 +02:00
// 2. Return count.
return JS : : Value ( count ) ;
}
2025-04-28 15:55:46 +02:00
// https://w3c.github.io/IndexedDB/#retrieve-a-value-from-an-object-store
WebIDL : : ExceptionOr < JS : : Value > retrieve_a_value_from_an_object_store ( JS : : Realm & realm , GC : : Ref < ObjectStore > store , GC : : Ref < IDBKeyRange > range )
{
// 1. Let record be the first record in store’ s list of records whose key is in range, if any.
auto record = store - > first_in_range ( range ) ;
// 2. If record was not found, return undefined.
if ( ! record . has_value ( ) )
return JS : : js_undefined ( ) ;
// 3. Let serialized be record’ s value. If an error occurs while reading the value from the underlying storage, return a newly created "NotReadableError" DOMException.
auto serialized = record - > value ;
// 4. Return ! StructuredDeserialize(serialized, targetRealm).
return MUST ( HTML : : structured_deserialize ( realm . vm ( ) , serialized , realm ) ) ;
}
2025-04-29 17:37:16 +02:00
// https://w3c.github.io/IndexedDB/#iterate-a-cursor
GC : : Ptr < IDBCursor > iterate_a_cursor ( JS : : Realm & realm , GC : : Ref < IDBCursor > cursor , GC : : Ptr < Key > key , GC : : Ptr < Key > primary_key , u64 count )
{
// 1. Let source be cursor’ s source.
2025-05-06 11:36:11 +02:00
auto source = cursor - > internal_source ( ) ;
2025-04-29 17:37:16 +02:00
// 2. Let direction be cursor’ s direction.
auto direction = cursor - > direction ( ) ;
// 3. Assert: if primaryKey is given, source is an index and direction is "next" or "prev".
auto direction_is_next_or_prev = direction = = Bindings : : IDBCursorDirection : : Next | | direction = = Bindings : : IDBCursorDirection : : Prev ;
if ( primary_key )
2025-05-06 11:36:11 +02:00
VERIFY ( source . has < GC : : Ref < Index > > ( ) & & direction_is_next_or_prev ) ;
2025-04-29 17:37:16 +02:00
// 4. Let records be the list of records in source.
Variant < ReadonlySpan < Record > , ReadonlySpan < IndexRecord > > records = source . visit (
2025-05-06 11:36:11 +02:00
[ ] ( GC : : Ref < ObjectStore > object_store ) - > Variant < ReadonlySpan < Record > , ReadonlySpan < IndexRecord > > {
return object_store - > records ( ) ;
2025-04-29 17:37:16 +02:00
} ,
2025-05-06 11:36:11 +02:00
[ ] ( GC : : Ref < Index > index ) - > Variant < ReadonlySpan < Record > , ReadonlySpan < IndexRecord > > {
return index - > records ( ) ;
2025-04-29 17:37:16 +02:00
} ) ;
// 5. Let range be cursor’ s range.
auto range = cursor - > range ( ) ;
// 6. Let position be cursor’ s position.
auto position = cursor - > position ( ) ;
// 7. Let object store position be cursor’ s object store position.
auto object_store_position = cursor - > object_store_position ( ) ;
// 8. If count is not given, let count be 1.
// NOTE: This is handled by the default parameter
auto next_requirements = [ & ] ( Variant < Record , IndexRecord > const & record ) - > bool {
2025-05-16 08:30:12 +02:00
// * If key is defined:
2025-04-29 17:37:16 +02:00
if ( key ) {
2025-05-16 08:30:12 +02:00
// * The record’ s key is greater than or equal to key.
2025-04-29 17:37:16 +02:00
auto is_greater_than_or_equal = record . visit (
[ ] ( Empty ) { VERIFY_NOT_REACHED ( ) ; } ,
[ key ] ( auto const & inner_record ) {
return Key : : greater_than ( inner_record . key , * key ) | | Key : : equals ( inner_record . key , * key ) ;
} ) ;
if ( ! is_greater_than_or_equal )
return false ;
}
2025-05-16 08:30:12 +02:00
// * If primaryKey is defined:
2025-04-29 17:37:16 +02:00
if ( primary_key ) {
auto const & inner_record = record . get < IndexRecord > ( ) ;
2025-05-16 08:30:12 +02:00
// * The record’ s key is equal to key and the record’ s value is greater than or equal to primaryKey
2025-04-29 17:37:16 +02:00
if ( ! ( Key : : equals ( inner_record . key , * key ) & & ( Key : : greater_than ( inner_record . value , * primary_key ) | | Key : : equals ( inner_record . value , * primary_key ) ) ) )
return false ;
2025-05-16 08:30:12 +02:00
// * The record’ s key is greater than key.
2025-04-29 17:37:16 +02:00
if ( ! Key : : greater_than ( inner_record . key , * key ) )
return false ;
}
2025-05-16 08:30:12 +02:00
// * If position is defined and source is an object store:
2025-05-06 11:36:11 +02:00
if ( position & & source . has < GC : : Ref < ObjectStore > > ( ) ) {
2025-04-29 17:37:16 +02:00
auto const & inner_record = record . get < Record > ( ) ;
2025-05-16 08:30:12 +02:00
// * The record’ s key is greater than position.
2025-04-29 17:37:16 +02:00
if ( ! Key : : greater_than ( inner_record . key , * position ) )
return false ;
}
2025-05-16 08:30:12 +02:00
// * If position is defined and source is an index:
2025-05-06 11:36:11 +02:00
if ( position & & source . has < GC : : Ref < Index > > ( ) ) {
2025-04-29 17:37:16 +02:00
auto const & inner_record = record . get < IndexRecord > ( ) ;
2025-05-16 08:30:12 +02:00
// * The record’ s key is equal to position and the record’ s value is greater than object store position
2025-04-29 17:37:16 +02:00
if ( ! ( Key : : equals ( inner_record . key , * position ) & & ( Key : : greater_than ( inner_record . value , * object_store_position ) ) ) )
return false ;
2025-05-16 08:30:12 +02:00
// * The record’ s key is greater than position.
2025-04-29 17:37:16 +02:00
if ( ! Key : : greater_than ( inner_record . key , * position ) )
return false ;
}
// * The record’ s key is in range.
auto is_in_range = record . visit (
[ ] ( Empty ) { VERIFY_NOT_REACHED ( ) ; } ,
[ range ] ( auto const & inner_record ) {
return range - > is_in_range ( inner_record . key ) ;
} ) ;
return is_in_range ;
} ;
auto next_unique_requirements = [ & ] ( Variant < Record , IndexRecord > const & record ) - > bool {
2025-05-16 08:30:12 +02:00
// * If key is defined:
2025-04-29 17:37:16 +02:00
if ( key ) {
2025-05-16 08:30:12 +02:00
// * The record’ s key is greater than or equal to key.
2025-04-29 17:37:16 +02:00
auto is_greater_than_or_equal = record . visit (
[ ] ( Empty ) { VERIFY_NOT_REACHED ( ) ; } ,
[ key ] ( auto const & inner_record ) {
return Key : : greater_than ( inner_record . key , * key ) | | Key : : equals ( inner_record . key , * key ) ;
} ) ;
if ( ! is_greater_than_or_equal )
return false ;
}
2025-05-16 08:30:12 +02:00
// * If position is defined:
2025-04-29 17:37:16 +02:00
if ( position ) {
2025-05-16 08:30:12 +02:00
// * The record’ s key is greater than position.
2025-04-29 17:37:16 +02:00
auto is_greater_than_position = record . visit (
[ ] ( Empty ) { VERIFY_NOT_REACHED ( ) ; } ,
[ position ] ( auto const & inner_record ) {
return Key : : greater_than ( inner_record . key , * position ) | | Key : : equals ( inner_record . key , * position ) ;
} ) ;
if ( ! is_greater_than_position )
return false ;
}
// * The record’ s key is in range.
auto is_in_range = record . visit (
[ ] ( Empty ) { VERIFY_NOT_REACHED ( ) ; } ,
[ range ] ( auto const & inner_record ) {
return range - > is_in_range ( inner_record . key ) ;
} ) ;
return is_in_range ;
} ;
auto prev_requirements = [ & ] ( Variant < Record , IndexRecord > const & record ) - > bool {
2025-05-16 08:30:12 +02:00
// * If key is defined:
2025-04-29 17:37:16 +02:00
if ( key ) {
2025-05-16 08:30:12 +02:00
// * The record’ s key is less than or equal to key.
2025-04-29 17:37:16 +02:00
auto is_less_than_or_equal = record . visit (
[ ] ( Empty ) { VERIFY_NOT_REACHED ( ) ; } ,
[ key ] ( auto const & inner_record ) {
return Key : : less_than ( inner_record . key , * key ) | | Key : : equals ( inner_record . key , * key ) ;
} ) ;
if ( ! is_less_than_or_equal )
return false ;
}
2025-05-16 08:30:12 +02:00
// * If primaryKey is defined:
2025-04-29 17:37:16 +02:00
if ( primary_key ) {
auto const & inner_record = record . get < IndexRecord > ( ) ;
2025-05-16 08:30:12 +02:00
// * The record’ s key is equal to key and the record’ s value is less than or equal to primaryKey
2025-04-29 17:37:16 +02:00
if ( ! ( Key : : equals ( inner_record . key , * key ) & & ( Key : : less_than ( inner_record . value , * primary_key ) | | Key : : equals ( inner_record . value , * primary_key ) ) ) )
return false ;
2025-05-16 08:30:12 +02:00
// * The record’ s key is less than key.
2025-04-29 17:37:16 +02:00
if ( ! Key : : less_than ( inner_record . key , * key ) )
return false ;
}
2025-05-16 08:30:12 +02:00
// * If position is defined and source is an object store:
2025-05-06 11:36:11 +02:00
if ( position & & source . has < GC : : Ref < ObjectStore > > ( ) ) {
2025-04-29 17:37:16 +02:00
auto const & inner_record = record . get < Record > ( ) ;
2025-05-16 08:30:12 +02:00
// * The record’ s key is less than position.
2025-04-29 17:37:16 +02:00
if ( ! Key : : less_than ( inner_record . key , * position ) )
return false ;
}
2025-05-16 08:30:12 +02:00
// * If position is defined and source is an index:
2025-05-06 11:36:11 +02:00
if ( position & & source . has < GC : : Ref < Index > > ( ) ) {
2025-04-29 17:37:16 +02:00
auto const & inner_record = record . get < IndexRecord > ( ) ;
2025-05-16 08:30:12 +02:00
// * The record’ s key is equal to position and the record’ s value is less than object store position
2025-04-29 17:37:16 +02:00
if ( ! ( Key : : equals ( inner_record . key , * position ) & & Key : : less_than ( inner_record . value , * object_store_position ) ) )
return false ;
2025-05-16 08:30:12 +02:00
// * The record’ s key is less than position.
2025-04-29 17:37:16 +02:00
if ( ! Key : : less_than ( inner_record . key , * position ) )
return false ;
}
// * The record’ s key is in range.
auto is_in_range = record . visit (
[ ] ( Empty ) { VERIFY_NOT_REACHED ( ) ; } ,
[ range ] ( auto const & inner_record ) {
return range - > is_in_range ( inner_record . key ) ;
} ) ;
return is_in_range ;
} ;
auto prev_unique_requirements = [ & ] ( Variant < Record , IndexRecord > const & record ) - > bool {
2025-05-16 08:30:12 +02:00
// * If key is defined:
2025-04-29 17:37:16 +02:00
if ( key ) {
2025-05-16 08:30:12 +02:00
// * The record’ s key is less than or equal to key.
2025-04-29 17:37:16 +02:00
auto is_less_than_or_equal = record . visit (
[ ] ( Empty ) { VERIFY_NOT_REACHED ( ) ; } ,
[ key ] ( auto const & inner_record ) {
return Key : : less_than ( inner_record . key , * key ) | | Key : : equals ( inner_record . key , * key ) ;
} ) ;
if ( ! is_less_than_or_equal )
return false ;
}
2025-05-16 08:30:12 +02:00
//* If position is defined:
2025-04-29 17:37:16 +02:00
if ( position ) {
2025-05-16 08:30:12 +02:00
// * The record’ s key is less than position.
2025-04-29 17:37:16 +02:00
auto is_less_than_position = record . visit (
[ ] ( Empty ) { VERIFY_NOT_REACHED ( ) ; } ,
[ position ] ( auto const & inner_record ) {
return Key : : less_than ( inner_record . key , * position ) | | Key : : equals ( inner_record . key , * position ) ;
} ) ;
if ( ! is_less_than_position )
return false ;
}
// * The record’ s key is in range.
auto is_in_range = record . visit (
[ ] ( Empty ) { VERIFY_NOT_REACHED ( ) ; } ,
[ range ] ( auto const & inner_record ) {
return range - > is_in_range ( inner_record . key ) ;
} ) ;
return is_in_range ;
} ;
// 9. While count is greater than 0:
Variant < Empty , Record , IndexRecord > found_record ;
while ( count > 0 ) {
// 1. Switch on direction:
switch ( direction ) {
case Bindings : : IDBCursorDirection : : Next : {
// Let found record be the first record in records which satisfy all of the following requirements:
found_record = records . visit ( [ & ] ( auto content ) - > Variant < Empty , Record , IndexRecord > {
auto value = content . first_matching ( next_requirements ) ;
if ( value . has_value ( ) )
return * value ;
return Empty { } ;
} ) ;
break ;
}
case Bindings : : IDBCursorDirection : : Nextunique : {
// Let found record be the first record in records which satisfy all of the following requirements:
found_record = records . visit ( [ & ] ( auto content ) - > Variant < Empty , Record , IndexRecord > {
auto value = content . first_matching ( next_unique_requirements ) ;
if ( value . has_value ( ) )
return * value ;
return Empty { } ;
} ) ;
break ;
}
case Bindings : : IDBCursorDirection : : Prev : {
// Let found record be the last record in records which satisfy all of the following requirements:
found_record = records . visit ( [ & ] ( auto content ) - > Variant < Empty , Record , IndexRecord > {
auto value = content . last_matching ( prev_requirements ) ;
if ( value . has_value ( ) )
return * value ;
return Empty { } ;
} ) ;
break ;
}
case Bindings : : IDBCursorDirection : : Prevunique : {
// Let temp record be the last record in records which satisfy all of the following requirements:
auto temp_record = records . visit ( [ & ] ( auto content ) - > Variant < Empty , Record , IndexRecord > {
auto value = content . last_matching ( prev_unique_requirements ) ;
if ( value . has_value ( ) )
return * value ;
return Empty { } ;
} ) ;
// If temp record is defined, let found record be the first record in records whose key is equal to temp record’ s key.
if ( ! temp_record . has < Empty > ( ) ) {
auto temp_record_key = temp_record . visit (
[ ] ( Empty ) - > GC : : Ref < Key > { VERIFY_NOT_REACHED ( ) ; } ,
[ ] ( auto const & record ) { return record . key ; } ) ;
found_record = records . visit ( [ & ] ( auto content ) - > Variant < Empty , Record , IndexRecord > {
auto value = content . first_matching ( [ & ] ( auto const & content_record ) {
return Key : : equals ( content_record . key , temp_record_key ) ;
} ) ;
if ( value . has_value ( ) )
return * value ;
return Empty { } ;
} ) ;
}
break ;
}
}
// 2. If found record is not defined, then:
if ( found_record . has < Empty > ( ) ) {
// 1. Set cursor’ s key to undefined.
cursor - > set_key ( nullptr ) ;
// 2. If source is an index, set cursor’ s object store position to undefined.
2025-05-06 11:36:11 +02:00
if ( source . has < GC : : Ref < Index > > ( ) )
2025-04-29 17:37:16 +02:00
cursor - > set_object_store_position ( nullptr ) ;
// 3. If cursor’ s key only flag is false, set cursor’ s value to undefined.
if ( ! cursor - > key_only ( ) )
cursor - > set_value ( JS : : js_undefined ( ) ) ;
// 4. Return null.
return nullptr ;
}
// 3. Let position be found record’ s key.
position = found_record . visit (
[ ] ( Empty ) - > GC : : Ref < Key > { VERIFY_NOT_REACHED ( ) ; } ,
[ ] ( auto val ) { return val . key ; } ) ;
// 4. If source is an index, let object store position be found record’ s value.
2025-05-06 11:36:11 +02:00
if ( source . has < GC : : Ref < Index > > ( ) )
2025-04-29 17:37:16 +02:00
object_store_position = found_record . get < IndexRecord > ( ) . value ;
// 5. Decrease count by 1.
count - - ;
}
// 10. Set cursor’ s position to position.
cursor - > set_position ( position ) ;
// 11. If source is an index, set cursor’ s object store position to object store position.
2025-05-06 11:36:11 +02:00
if ( source . has < GC : : Ref < Index > > ( ) )
2025-04-29 17:37:16 +02:00
cursor - > set_object_store_position ( object_store_position ) ;
// 12. Set cursor’ s key to found record’ s key.
cursor - > set_key ( found_record . visit (
[ ] ( Empty ) - > GC : : Ref < Key > { VERIFY_NOT_REACHED ( ) ; } ,
[ ] ( auto val ) { return val . key ; } ) ) ;
// 13. If cursor’ s key only flag is false, then:
if ( ! cursor - > key_only ( ) ) {
// 1. Let serialized be found record’ s value if source is an object store, or found record’ s referenced value otherwise.
auto serialized = source . visit (
2025-05-06 11:36:11 +02:00
[ & ] ( GC : : Ref < ObjectStore > ) {
2025-04-29 17:37:16 +02:00
return found_record . get < Record > ( ) . value ;
} ,
2025-05-06 11:36:11 +02:00
[ & ] ( GC : : Ref < Index > index ) {
return index - > referenced_value ( found_record . get < IndexRecord > ( ) ) ;
2025-04-29 17:37:16 +02:00
} ) ;
// 2. Set cursor’ s value to ! StructuredDeserialize(serialized, targetRealm)
cursor - > set_value ( MUST ( HTML : : structured_deserialize ( realm . vm ( ) , serialized , realm ) ) ) ;
}
// 14. Set cursor’ s got value flag to true.
cursor - > set_got_value ( true ) ;
// 15. Return cursor.
return cursor ;
}
2025-05-08 10:04:43 +02:00
// https://w3c.github.io/IndexedDB/#clear-an-object-store
JS : : Value clear_an_object_store ( GC : : Ref < ObjectStore > store )
{
// 1. Remove all records from store.
store - > clear_records ( ) ;
// 2. In all indexes which reference store, remove all records.
for ( auto const & [ name , index ] : store - > index_set ( ) ) {
index - > clear_records ( ) ;
}
// 3. Return undefined.
return JS : : js_undefined ( ) ;
}
2025-05-08 15:27:11 +02:00
// https://w3c.github.io/IndexedDB/#retrieve-a-key-from-an-object-store
JS : : Value retrieve_a_key_from_an_object_store ( JS : : Realm & realm , GC : : Ref < ObjectStore > store , GC : : Ref < IDBKeyRange > range )
{
// 1. Let record be the first record in store’ s list of records whose key is in range, if any.
auto record = store - > first_in_range ( range ) ;
// 2. If record was not found, return undefined.
if ( ! record . has_value ( ) )
return JS : : js_undefined ( ) ;
// 3. Return the result of converting a key to a value with record’ s key.
return convert_a_key_to_a_value ( realm , record . value ( ) . key ) ;
}
2025-05-08 23:36:18 +02:00
// https://w3c.github.io/IndexedDB/#retrieve-multiple-values-from-an-object-store
GC : : Ref < JS : : Array > retrieve_multiple_values_from_an_object_store ( JS : : Realm & realm , GC : : Ref < ObjectStore > store , GC : : Ref < IDBKeyRange > range , Optional < WebIDL : : UnsignedLong > count )
{
// 1. If count is not given or is 0 (zero), let count be infinity.
if ( count . has_value ( ) & & * count = = 0 )
count = OptionalNone ( ) ;
// 2. Let records be a list containing the first count records in store’ s list of records whose key is in range.
auto records = store - > first_n_in_range ( range , count ) ;
// 3. Let list be an empty list.
auto list = MUST ( JS : : Array : : create ( realm , records . size ( ) ) ) ;
// 4. For each record of records:
for ( u32 i = 0 ; i < records . size ( ) ; + + i ) {
auto & record = records [ i ] ;
// 1. Let serialized be record’ s value. If an error occurs while reading the value from the underlying storage, return a newly created "NotReadableError" DOMException.
auto serialized = record . value ;
// 2. Let entry be ! StructuredDeserialize(serialized, targetRealm).
auto entry = MUST ( HTML : : structured_deserialize ( realm . vm ( ) , serialized , realm ) ) ;
// 3. Append entry to list.
MUST ( list - > create_data_property_or_throw ( i , entry ) ) ;
}
// 5. Return list converted to a sequence<any>.
return list ;
}
2025-05-08 23:47:38 +02:00
// https://w3c.github.io/IndexedDB/#retrieve-multiple-keys-from-an-object-store
GC : : Ref < JS : : Array > retrieve_multiple_keys_from_an_object_store ( JS : : Realm & realm , GC : : Ref < ObjectStore > store , GC : : Ref < IDBKeyRange > range , Optional < WebIDL : : UnsignedLong > count )
{
// 1. If count is not given or is 0 (zero), let count be infinity.
if ( count . has_value ( ) & & * count = = 0 )
count = OptionalNone ( ) ;
// 2. Let records be a list containing the first count records in store’ s list of records whose key is in range.
auto records = store - > first_n_in_range ( range , count ) ;
// 3. Let list be an empty list.
auto list = MUST ( JS : : Array : : create ( realm , records . size ( ) ) ) ;
// 4. For each record of records:
for ( u32 i = 0 ; i < records . size ( ) ; + + i ) {
auto & record = records [ i ] ;
// 1. Let entry be the result of converting a key to a value with record’ s key.
auto entry = convert_a_key_to_a_value ( realm , record . key ) ;
// 2. Append entry to list.
MUST ( list - > create_data_property_or_throw ( i , entry ) ) ;
}
// 5. Return list converted to a sequence<any>.
return list ;
}
2025-05-13 22:18:14 +02:00
// https://w3c.github.io/IndexedDB/#retrieve-a-referenced-value-from-an-index
JS : : Value retrieve_a_referenced_value_from_an_index ( JS : : Realm & realm , GC : : Ref < Index > index , GC : : Ref < IDBKeyRange > range )
{
// 1. Let record be the first record in index’ s list of records whose key is in range, if any.
auto record = index - > first_in_range ( range ) ;
// 2. If record was not found, return undefined.
if ( ! record . has_value ( ) )
return JS : : js_undefined ( ) ;
// 3. Let serialized be record’ s referenced value.
auto serialized = index - > referenced_value ( * record ) ;
// 4. Return ! StructuredDeserialize(serialized, targetRealm).
return MUST ( HTML : : structured_deserialize ( realm . vm ( ) , serialized , realm ) ) ;
}
2025-05-13 22:21:57 +02:00
// https://w3c.github.io/IndexedDB/#retrieve-a-value-from-an-index
JS : : Value retrieve_a_value_from_an_index ( JS : : Realm & realm , GC : : Ref < Index > index , GC : : Ref < IDBKeyRange > range )
{
// 1. Let record be the first record in index’ s list of records whose key is in range, if any.
auto record = index - > first_in_range ( range ) ;
// 2. If record was not found, return undefined.
if ( ! record . has_value ( ) )
return JS : : js_undefined ( ) ;
// 3. Return the result of converting a key to a value with record’ s value.
return convert_a_key_to_a_value ( realm , record - > value ) ;
}
2025-05-13 22:41:18 +02:00
// https://w3c.github.io/IndexedDB/#retrieve-multiple-referenced-values-from-an-index
GC : : Ref < JS : : Array > retrieve_multiple_referenced_values_from_an_index ( JS : : Realm & realm , GC : : Ref < Index > index , GC : : Ref < IDBKeyRange > range , Optional < WebIDL : : UnsignedLong > count )
{
// 1. If count is not given or is 0 (zero), let count be infinity.
if ( count . has_value ( ) & & * count = = 0 )
count = OptionalNone ( ) ;
// 2. Let records be a list containing the first count records in index’ s list of records whose key is in range.
auto records = index - > first_n_in_range ( range , count ) ;
// 3. Let list be an empty list.
auto list = MUST ( JS : : Array : : create ( realm , records . size ( ) ) ) ;
// 4. For each record of records:
for ( u32 i = 0 ; i < records . size ( ) ; + + i ) {
auto & record = records [ i ] ;
// 1. Let serialized be record’ s referenced value.
auto serialized = index - > referenced_value ( record ) ;
// 2. Let entry be ! StructuredDeserialize(serialized, targetRealm).
auto entry = MUST ( HTML : : structured_deserialize ( realm . vm ( ) , serialized , realm ) ) ;
// 3. Append entry to list.
MUST ( list - > create_data_property_or_throw ( i , entry ) ) ;
}
// 5. Return list converted to a sequence<any>.
return list ;
}
2025-05-13 22:46:23 +02:00
// https://w3c.github.io/IndexedDB/#retrieve-multiple-values-from-an-index
GC : : Ref < JS : : Array > retrieve_multiple_values_from_an_index ( JS : : Realm & realm , GC : : Ref < Index > index , GC : : Ref < IDBKeyRange > range , Optional < WebIDL : : UnsignedLong > count )
{
// 1. If count is not given or is 0 (zero), let count be infinity.
if ( count . has_value ( ) & & * count = = 0 )
count = OptionalNone ( ) ;
// 2. Let records be a list containing the first count records in index’ s list of records whose key is in range.
auto records = index - > first_n_in_range ( range , count ) ;
// 3. Let list be an empty list.
auto list = MUST ( JS : : Array : : create ( realm , records . size ( ) ) ) ;
// 4. For each record of records:
for ( u32 i = 0 ; i < records . size ( ) ; + + i ) {
auto & record = records [ i ] ;
// 1. Let entry be the result of converting a key to a value with record’ s value.
auto entry = convert_a_key_to_a_value ( realm , record . value ) ;
// 2. Append entry to list.
MUST ( list - > create_data_property_or_throw ( i , entry ) ) ;
}
// 7. Return list converted to a sequence<any>.
return list ;
}
2025-05-16 08:26:03 +02:00
// https://w3c.github.io/IndexedDB/#queue-a-database-task
void queue_a_database_task ( GC : : Ref < GC : : Function < void ( ) > > steps )
{
// To queue a database task, perform queue a task on the database access task source.
HTML : : queue_a_task ( HTML : : Task : : Source : : DatabaseAccess , nullptr , nullptr , steps ) ;
}
2024-11-04 19:03:26 +01:00
}