diff --git a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp index 146fb116b57..e04592bd16f 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp +++ b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp @@ -1312,4 +1312,117 @@ void delete_records_from_an_object_store(GC::Ref store, GC::Ref> store_a_record_into_an_object_store(JS::Realm& realm, GC::Ref store, JS::Value value, GC::Ptr key, bool no_overwrite) +{ + // 1. If store uses a key generator, then: + if (store->key_generator().has_value()) { + // 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(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) { + auto key_range = IDBKeyRange::create(realm, key, key, false, false); + 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. + auto index_key = TRY(extract_a_key_from_a_value_using_a_key_path(realm, value, index->key_path(), index->multi_entry())); + + // 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. + if (index_key.is_error()) + continue; + + auto index_key_value = index_key.value(); + auto index_multi_entry = index->multi_entry(); + auto index_key_is_array = index_key_value->type() == Key::KeyType::Array; + 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. + if ((!index_multi_entry || !index_key_is_array) && index_is_unique && index->has_record_with_key(index_key_value)) + 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) { + for (auto const& subkey : index_key_value->subkeys()) { + if (index->has_record_with_key(*subkey)) + return WebIDL::ConstraintError::create(realm, "Record already exists in index"_string); + } + } + + // 5. If index’s multiEntry flag is false, or if index key is not an array key + // 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. + if (!index_multi_entry || !index_key_is_array) { + // FIXME: + // Record index_record = { + // .key = index_key_value, + // .value = MUST(HTML::structured_serialize_for_storage(realm.vm(), key)), + // }; + // index->store_a_record(index_record); + } + + // 6. If index’s multiEntry flag is true and index key is an array key, + // 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. + if (index_multi_entry && index_key_is_array) { + for (auto const& subkey : index_key_value->subkeys()) { + (void)subkey; + // FIXME: + // Record index_record = { + // .key = *subkey, + // .value = MUST(HTML::structured_serialize_for_storage(realm.vm(), key)), + // }; + // index->store_a_record(index_record); + } + } + } + + // 6. Return key. + return key; +} + } diff --git a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h index 29ae317ae31..e9c208bd082 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h +++ b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h @@ -40,5 +40,6 @@ ErrorOr generate_a_key(GC::Ref); void possibly_update_the_key_generator(GC::Ref, GC::Ref); void inject_a_key_into_a_value_using_a_key_path(JS::Realm&, JS::Value, GC::Ref, KeyPath const&); void delete_records_from_an_object_store(GC::Ref, GC::Ref); +WebIDL::ExceptionOr> store_a_record_into_an_object_store(JS::Realm&, GC::Ref, JS::Value, GC::Ptr, bool); } diff --git a/Libraries/LibWeb/IndexedDB/Internal/Index.cpp b/Libraries/LibWeb/IndexedDB/Internal/Index.cpp index d347f1dd49f..924f8acdb51 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Index.cpp +++ b/Libraries/LibWeb/IndexedDB/Internal/Index.cpp @@ -48,4 +48,13 @@ void Index::set_name(String name) m_name = move(name); } +bool Index::has_record_with_key(GC::Ref key) +{ + auto index = m_records.find_if([&key](auto const& record) { + return record.key == key; + }); + + return index != m_records.end(); +} + } diff --git a/Libraries/LibWeb/IndexedDB/Internal/Index.h b/Libraries/LibWeb/IndexedDB/Internal/Index.h index 6296c8b805c..641d92817bd 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Index.h +++ b/Libraries/LibWeb/IndexedDB/Internal/Index.h @@ -39,6 +39,8 @@ public: [[nodiscard]] AK::ReadonlySpan records() const { return m_records; } [[nodiscard]] KeyPath const& key_path() const { return m_key_path; } + [[nodiscard]] bool has_record_with_key(GC::Ref key); + protected: virtual void visit_edges(Visitor&) override; diff --git a/Libraries/LibWeb/IndexedDB/Internal/ObjectStore.cpp b/Libraries/LibWeb/IndexedDB/Internal/ObjectStore.cpp index e79c1217da0..79825d59f28 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/ObjectStore.cpp +++ b/Libraries/LibWeb/IndexedDB/Internal/ObjectStore.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include @@ -47,4 +48,23 @@ void ObjectStore::remove_records_in_range(GC::Ref range) }); } +bool ObjectStore::has_record_with_key(GC::Ref key) +{ + auto index = m_records.find_if([&key](auto const& record) { + return record.key == key; + }); + + return index != m_records.end(); +} + +void ObjectStore::store_a_record(Record const& record) +{ + m_records.append(record); + + // NOTE: 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. + AK::quick_sort(m_records, [](auto const& a, auto const& b) { + return Key::compare_two_keys(a.key, b.key) < 0; + }); +} + } diff --git a/Libraries/LibWeb/IndexedDB/Internal/ObjectStore.h b/Libraries/LibWeb/IndexedDB/Internal/ObjectStore.h index bda33d6a098..697b92860d5 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/ObjectStore.h +++ b/Libraries/LibWeb/IndexedDB/Internal/ObjectStore.h @@ -49,6 +49,8 @@ public: GC::Ref database() const { return m_database; } void remove_records_in_range(GC::Ref range); + bool has_record_with_key(GC::Ref key); + void store_a_record(Record const& record); protected: virtual void visit_edges(Visitor&) override;