diff --git a/Libraries/LibWeb/IndexedDB/IDBCursor.h b/Libraries/LibWeb/IndexedDB/IDBCursor.h index 56937c333c6..14054e7653a 100644 --- a/Libraries/LibWeb/IndexedDB/IDBCursor.h +++ b/Libraries/LibWeb/IndexedDB/IDBCursor.h @@ -40,6 +40,13 @@ public: [[nodiscard]] bool key_only() const { return m_key_only; } [[nodiscard]] bool got_value() const { return m_got_value; } + void set_request(GC::Ptr request) { m_request = request; } + void set_position(GC::Ptr position) { m_position = position; } + void set_got_value(bool got_value) { m_got_value = got_value; } + void set_key(GC::Ptr key) { m_key = key; } + void set_value(JS::Value value) { m_value = value; } + void set_object_store_position(GC::Ptr object_store_position) { m_object_store_position = object_store_position; } + protected: explicit IDBCursor(JS::Realm&, GC::Ref, GC::Ptr, Bindings::IDBCursorDirection, bool, GC::Ptr, JS::Value, CursorSource, GC::Ref, bool); virtual void initialize(JS::Realm&) override; diff --git a/Libraries/LibWeb/IndexedDB/IDBIndex.cpp b/Libraries/LibWeb/IndexedDB/IDBIndex.cpp index 8d4df08df87..56920cd80e8 100644 --- a/Libraries/LibWeb/IndexedDB/IDBIndex.cpp +++ b/Libraries/LibWeb/IndexedDB/IDBIndex.cpp @@ -101,4 +101,19 @@ JS::Value IDBIndex::key_path() const }); } +// https://w3c.github.io/IndexedDB/#index-referenced-value +HTML::SerializationRecord IDBIndex::get_referenced_value(IndexRecord const& index_record) const +{ + // Records in an index are said to have a referenced value. + // This is the value of the record in the index’s referenced object store which has a key equal to the index’s record’s value. + return m_index + ->object_store() + ->records() + .first_matching([&](auto const& store_record) { + return Key::equals(store_record.key, index_record.value); + }) + .value() + .value; +} + } diff --git a/Libraries/LibWeb/IndexedDB/IDBIndex.h b/Libraries/LibWeb/IndexedDB/IDBIndex.h index ba8ef9f4815..2189a9b4167 100644 --- a/Libraries/LibWeb/IndexedDB/IDBIndex.h +++ b/Libraries/LibWeb/IndexedDB/IDBIndex.h @@ -32,7 +32,8 @@ public: // The transaction of an index handle is the transaction of its associated object store handle. GC::Ref transaction() { return m_object_store_handle->transaction(); } GC::Ref index() { return m_index; } - GC::Ref store() { return m_object_store_handle; } + + HTML::SerializationRecord get_referenced_value(IndexRecord const& index_record) const; protected: explicit IDBIndex(JS::Realm&, GC::Ref, GC::Ref); diff --git a/Libraries/LibWeb/IndexedDB/IDBObjectStore.h b/Libraries/LibWeb/IndexedDB/IDBObjectStore.h index 919c3edde78..4571e62a3ad 100644 --- a/Libraries/LibWeb/IndexedDB/IDBObjectStore.h +++ b/Libraries/LibWeb/IndexedDB/IDBObjectStore.h @@ -10,6 +10,7 @@ #include #include #include +#include #include namespace Web::IndexedDB { diff --git a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp index 61bc0de74ed..2824ed3aecd 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp +++ b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include #include #include @@ -1475,4 +1477,366 @@ WebIDL::ExceptionOr retrieve_a_value_from_an_object_store(JS::Realm& return MUST(HTML::structured_deserialize(realm.vm(), serialized, realm)); } +// https://w3c.github.io/IndexedDB/#iterate-a-cursor +GC::Ptr iterate_a_cursor(JS::Realm& realm, GC::Ref cursor, GC::Ptr key, GC::Ptr primary_key, u64 count) +{ + // 1. Let source be cursor’s source. + auto source = cursor->source(); + + // 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) + VERIFY(source.has>() && direction_is_next_or_prev); + + // 4. Let records be the list of records in source. + Variant, ReadonlySpan> records = source.visit( + [](GC::Ref object_store) -> Variant, ReadonlySpan> { + return object_store->store()->records(); + }, + [](GC::Ref index) -> Variant, ReadonlySpan> { + return index->index()->records(); + }); + + // 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 const& record) -> bool { + // * If key is defined, + if (key) { + // * the record’s key is greater than or equal to key. + 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; + } + + // * If primaryKey is defined, + if (primary_key) { + auto const& inner_record = record.get(); + + // * the record’s key is equal to key and the record’s value is greater than or equal to primaryKey, + if (!(Key::equals(inner_record.key, *key) && (Key::greater_than(inner_record.value, *primary_key) || Key::equals(inner_record.value, *primary_key)))) + return false; + + // * or the record’s key is greater than key. + if (!Key::greater_than(inner_record.key, *key)) + return false; + } + + // * If position is defined, and source is an object store, + if (position && source.has>()) { + auto const& inner_record = record.get(); + + // * the record’s key is greater than position. + if (!Key::greater_than(inner_record.key, *position)) + return false; + } + + // * If position is defined, and source is an index, + if (position && source.has>()) { + auto const& inner_record = record.get(); + + // * the record’s key is equal to position and the record’s value is greater than object store position + if (!(Key::equals(inner_record.key, *position) && (Key::greater_than(inner_record.value, *object_store_position)))) + return false; + + // * or the record’s key is greater than position. + 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 const& record) -> bool { + // * If key is defined, + if (key) { + // * the record’s key is greater than or equal to key. + 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; + } + + // * If position is defined, + if (position) { + // * the record’s key is greater than position. + 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 const& record) -> bool { + // * If key is defined, + if (key) { + // * the record’s key is less than or equal to key. + 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; + } + + // * If primaryKey is defined, + if (primary_key) { + auto const& inner_record = record.get(); + + // * the record’s key is equal to key and the record’s value is less than or equal to primaryKey, + if (!(Key::equals(inner_record.key, *key) && (Key::less_than(inner_record.value, *primary_key) || Key::equals(inner_record.value, *primary_key)))) + return false; + + // * or the record’s key is less than key. + if (!Key::less_than(inner_record.key, *key)) + return false; + } + + // * If position is defined, and source is an object store, + if (position && source.has>()) { + auto const& inner_record = record.get(); + + // * the record’s key is less than position. + if (!Key::less_than(inner_record.key, *position)) + return false; + } + + // * If position is defined, and source is an index, + if (position && source.has>()) { + auto const& inner_record = record.get(); + + // * the record’s key is equal to position and the record’s value is less than object store position + if (!(Key::equals(inner_record.key, *position) && Key::less_than(inner_record.value, *object_store_position))) + return false; + + // * or the record’s key is less than position. + 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 const& record) -> bool { + // * If key is defined, + if (key) { + // * the record’s key is less than or equal to key. + 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; + } + + //* If position is defined, + if (position) { + // * the record’s key is less than position. + 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 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 { + 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 { + 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 { + 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 { + 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()) { + auto temp_record_key = temp_record.visit( + [](Empty) -> GC::Ref { VERIFY_NOT_REACHED(); }, + [](auto const& record) { return record.key; }); + + found_record = records.visit([&](auto content) -> Variant { + 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()) { + // 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. + if (source.has>()) + 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 { VERIFY_NOT_REACHED(); }, + [](auto val) { return val.key; }); + + // 4. If source is an index, let object store position be found record’s value. + if (source.has>()) + object_store_position = found_record.get().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. + if (source.has>()) + 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 { 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( + [&](GC::Ref) { + return found_record.get().value; + }, + [&](GC::Ref index) { + return index->get_referenced_value(found_record.get()); + }); + + // 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; +} + } diff --git a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h index e7fb80ff6fc..1bcb73d68fd 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h +++ b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h @@ -44,5 +44,6 @@ WebIDL::ExceptionOr> store_a_record_into_an_object_store(JS::Realm& WebIDL::ExceptionOr> convert_a_value_to_a_key_range(JS::Realm&, Optional, bool = false); JS::Value count_the_records_in_a_range(GC::Ref, GC::Ref); WebIDL::ExceptionOr retrieve_a_value_from_an_object_store(JS::Realm&, GC::Ref, GC::Ref); +GC::Ptr iterate_a_cursor(JS::Realm&, GC::Ref, GC::Ptr = nullptr, GC::Ptr = nullptr, u64 = 1); } diff --git a/Libraries/LibWeb/IndexedDB/Internal/ObjectStore.h b/Libraries/LibWeb/IndexedDB/Internal/ObjectStore.h index fbe2e637add..1222d5c3886 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/ObjectStore.h +++ b/Libraries/LibWeb/IndexedDB/Internal/ObjectStore.h @@ -48,6 +48,7 @@ public: AK::HashMap>& index_set() { return m_indexes; } GC::Ref database() const { return m_database; } + ReadonlySpan records() const { return m_records; } void remove_records_in_range(GC::Ref range); bool has_record_with_key(GC::Ref key);