1
0
Fork 0
mirror of https://github.com/LadybirdBrowser/ladybird.git synced 2025-06-09 09:34:57 +09:00

LibWeb/IDB: Implement recent spec changes

This commit is contained in:
stelar7 2025-05-06 11:36:11 +02:00 committed by Sam Atkins
parent 6cbb5d2785
commit 9d5f6108e4
Notes: github-actions[bot] 2025-05-06 12:31:37 +00:00
9 changed files with 71 additions and 56 deletions

View file

@ -16,23 +16,22 @@ GC_DEFINE_ALLOCATOR(IDBCursor);
IDBCursor::~IDBCursor() = default; IDBCursor::~IDBCursor() = default;
IDBCursor::IDBCursor(JS::Realm& realm, GC::Ref<IDBTransaction> transaction, GC::Ptr<Key> position, Bindings::IDBCursorDirection direction, bool got_value, GC::Ptr<Key> key, JS::Value value, CursorSource source, GC::Ref<IDBKeyRange> range, bool key_only) IDBCursor::IDBCursor(JS::Realm& realm, CursorSourceHandle source_handle, GC::Ptr<Key> position, Bindings::IDBCursorDirection direction, bool got_value, GC::Ptr<Key> key, JS::Value value, GC::Ref<IDBKeyRange> range, bool key_only)
: PlatformObject(realm) : PlatformObject(realm)
, m_transaction(transaction)
, m_position(position) , m_position(position)
, m_direction(direction) , m_direction(direction)
, m_got_value(got_value) , m_got_value(got_value)
, m_key(key) , m_key(key)
, m_value(value) , m_value(value)
, m_source(source) , m_source_handle(source_handle)
, m_range(range) , m_range(range)
, m_key_only(key_only) , m_key_only(key_only)
{ {
} }
GC::Ref<IDBCursor> IDBCursor::create(JS::Realm& realm, GC::Ref<IDBTransaction> transaction, GC::Ptr<Key> position, Bindings::IDBCursorDirection direction, bool got_value, GC::Ptr<Key> key, JS::Value value, CursorSource source, GC::Ref<IDBKeyRange> range, bool key_only) GC::Ref<IDBCursor> IDBCursor::create(JS::Realm& realm, CursorSourceHandle source_handle, GC::Ptr<Key> position, Bindings::IDBCursorDirection direction, bool got_value, GC::Ptr<Key> key, JS::Value value, GC::Ref<IDBKeyRange> range, bool key_only)
{ {
return realm.create<IDBCursor>(realm, transaction, position, direction, got_value, key, value, source, range, key_only); return realm.create<IDBCursor>(realm, source_handle, position, direction, got_value, key, value, range, key_only);
} }
void IDBCursor::initialize(JS::Realm& realm) void IDBCursor::initialize(JS::Realm& realm)
@ -44,18 +43,35 @@ void IDBCursor::initialize(JS::Realm& realm)
void IDBCursor::visit_edges(Visitor& visitor) void IDBCursor::visit_edges(Visitor& visitor)
{ {
Base::visit_edges(visitor); Base::visit_edges(visitor);
visitor.visit(m_transaction);
visitor.visit(m_position); visitor.visit(m_position);
visitor.visit(m_object_store_position); visitor.visit(m_object_store_position);
visitor.visit(m_key); visitor.visit(m_key);
visitor.visit(m_range); visitor.visit(m_range);
visitor.visit(m_request); visitor.visit(m_request);
m_source.visit([&](auto& source) { m_source_handle.visit([&](auto& source) {
visitor.visit(source); visitor.visit(source);
}); });
} }
// https://w3c.github.io/IndexedDB/#cursor-transaction
GC::Ref<IDBTransaction> IDBCursor::transaction()
{
// A cursor has a transaction, which is the transaction from the cursors source handle.
return m_source_handle.visit(
[](GC::Ref<IDBObjectStore> object_store) { return object_store->transaction(); },
[](GC::Ref<IDBIndex> index) { return index->transaction(); });
}
// https://w3c.github.io/IndexedDB/#cursor-source
CursorSource IDBCursor::internal_source()
{
// A cursor has a source, which is an index or an object store from the cursors source handle.
return m_source_handle.visit(
[](GC::Ref<IDBObjectStore> object_store) -> CursorSource { return object_store->store(); },
[](GC::Ref<IDBIndex> index) -> CursorSource { return index->index(); });
}
// https://w3c.github.io/IndexedDB/#dom-idbcursor-key // https://w3c.github.io/IndexedDB/#dom-idbcursor-key
JS::Value IDBCursor::key() JS::Value IDBCursor::key()
{ {
@ -125,8 +141,8 @@ WebIDL::ExceptionOr<void> IDBCursor::continue_(JS::Value key)
return WebIDL::ExceptionOr<JS::Value>(iterate_a_cursor(realm, *this, key_value)); return WebIDL::ExceptionOr<JS::Value>(iterate_a_cursor(realm, *this, key_value));
}); });
// 11. Run asynchronously execute a request with this's source, operation, and request. // 11. Run asynchronously execute a request with thiss source handle, operation, and request.
asynchronously_execute_a_request(realm, GC::Ref(*this), operation, request); asynchronously_execute_a_request(realm, source_handle(), operation, request);
dbgln_if(IDB_DEBUG, "Executing request for cursor continue with uuid {}", request->uuid()); dbgln_if(IDB_DEBUG, "Executing request for cursor continue with uuid {}", request->uuid());
return {}; return {};

View file

@ -17,7 +17,8 @@
namespace Web::IndexedDB { namespace Web::IndexedDB {
using CursorSource = Variant<GC::Ref<IDBObjectStore>, GC::Ref<IDBIndex>>; using CursorSourceHandle = Variant<GC::Ref<IDBObjectStore>, GC::Ref<IDBIndex>>;
using CursorSource = Variant<GC::Ref<ObjectStore>, GC::Ref<Index>>;
// https://w3c.github.io/IndexedDB/#cursor-interface // https://w3c.github.io/IndexedDB/#cursor-interface
class IDBCursor : public Bindings::PlatformObject { class IDBCursor : public Bindings::PlatformObject {
@ -26,20 +27,22 @@ class IDBCursor : public Bindings::PlatformObject {
public: public:
virtual ~IDBCursor() override; virtual ~IDBCursor() override;
[[nodiscard]] static GC::Ref<IDBCursor> create(JS::Realm&, GC::Ref<IDBTransaction>, GC::Ptr<Key>, Bindings::IDBCursorDirection, bool, GC::Ptr<Key>, JS::Value, CursorSource, GC::Ref<IDBKeyRange>, bool); [[nodiscard]] static GC::Ref<IDBCursor> create(JS::Realm&, CursorSourceHandle, GC::Ptr<Key>, Bindings::IDBCursorDirection, bool, GC::Ptr<Key>, JS::Value, GC::Ref<IDBKeyRange>, bool);
[[nodiscard]] CursorSource source() { return m_source; } [[nodiscard]] CursorSourceHandle source_handle() { return m_source_handle; }
[[nodiscard]] Bindings::IDBCursorDirection direction() { return m_direction; } [[nodiscard]] Bindings::IDBCursorDirection direction() { return m_direction; }
[[nodiscard]] JS::Value key(); [[nodiscard]] JS::Value key();
[[nodiscard]] JS::Value value() { return m_value.value_or(JS::js_undefined()); } [[nodiscard]] JS::Value value() { return m_value.value_or(JS::js_undefined()); }
[[nodiscard]] GC::Ptr<IDBRequest> request() { return m_request; } [[nodiscard]] GC::Ptr<IDBRequest> request() { return m_request; }
[[nodiscard]] GC::Ref<IDBTransaction> transaction() { return m_transaction; }
[[nodiscard]] GC::Ref<IDBKeyRange> range() { return m_range; } [[nodiscard]] GC::Ref<IDBKeyRange> range() { return m_range; }
[[nodiscard]] GC::Ptr<Key> position() { return m_position; } [[nodiscard]] GC::Ptr<Key> position() { return m_position; }
[[nodiscard]] GC::Ptr<Key> object_store_position() { return m_object_store_position; } [[nodiscard]] GC::Ptr<Key> object_store_position() { return m_object_store_position; }
[[nodiscard]] bool key_only() const { return m_key_only; } [[nodiscard]] bool key_only() const { return m_key_only; }
[[nodiscard]] bool got_value() const { return m_got_value; } [[nodiscard]] bool got_value() const { return m_got_value; }
[[nodiscard]] GC::Ref<IDBTransaction> transaction();
[[nodiscard]] CursorSource internal_source();
void set_request(GC::Ptr<IDBRequest> request) { m_request = request; } void set_request(GC::Ptr<IDBRequest> request) { m_request = request; }
void set_position(GC::Ptr<Key> position) { m_position = position; } void set_position(GC::Ptr<Key> position) { m_position = position; }
void set_got_value(bool got_value) { m_got_value = got_value; } void set_got_value(bool got_value) { m_got_value = got_value; }
@ -50,14 +53,11 @@ public:
WebIDL::ExceptionOr<void> continue_(JS::Value); WebIDL::ExceptionOr<void> continue_(JS::Value);
protected: protected:
explicit IDBCursor(JS::Realm&, GC::Ref<IDBTransaction>, GC::Ptr<Key>, Bindings::IDBCursorDirection, bool, GC::Ptr<Key>, JS::Value, CursorSource, GC::Ref<IDBKeyRange>, bool); explicit IDBCursor(JS::Realm&, CursorSourceHandle, GC::Ptr<Key>, Bindings::IDBCursorDirection, bool, GC::Ptr<Key>, JS::Value, GC::Ref<IDBKeyRange>, bool);
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Visitor& visitor) override; virtual void visit_edges(Visitor& visitor) override;
private: private:
// A cursor has a transaction, the transaction that was active when the cursor was created.
GC::Ref<IDBTransaction> m_transaction;
// A cursor has a position within its range. // A cursor has a position within its range.
GC::Ptr<Key> m_position; GC::Ptr<Key> m_position;
@ -74,8 +74,8 @@ private:
GC::Ptr<Key> m_key; GC::Ptr<Key> m_key;
Optional<JS::Value> m_value; Optional<JS::Value> m_value;
// A cursor has a source that indicates which index or an object store is associated with the records over which the cursor is iterating. // A cursor has a source handle, which is the index handle or the object store handle that opened the cursor.
CursorSource m_source; CursorSourceHandle m_source_handle;
// A cursor has a range of records in either an index or an object store. // A cursor has a range of records in either an index or an object store.
GC::Ref<IDBKeyRange> m_range; GC::Ref<IDBKeyRange> m_range;

View file

@ -2,7 +2,7 @@
[Exposed=(Window,Worker)] [Exposed=(Window,Worker)]
interface IDBCursor { interface IDBCursor {
readonly attribute (IDBObjectStore or IDBIndex) source; [ImplementedAs=source_handle] readonly attribute (IDBObjectStore or IDBIndex) source;
readonly attribute IDBCursorDirection direction; readonly attribute IDBCursorDirection direction;
readonly attribute any key; readonly attribute any key;
[FIXME] readonly attribute any primaryKey; [FIXME] readonly attribute any primaryKey;

View file

@ -101,19 +101,4 @@ 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 indexs referenced object store which has a key equal to the indexs records value.
return m_index
->object_store()
->records()
.first_matching([&](auto const& store_record) {
return Key::equals(store_record.key, index_record.value);
})
.value()
.value;
}
} }

View file

@ -33,8 +33,6 @@ public:
GC::Ref<IDBTransaction> transaction() { return m_object_store_handle->transaction(); } GC::Ref<IDBTransaction> transaction() { return m_object_store_handle->transaction(); }
GC::Ref<Index> index() { return m_index; } GC::Ref<Index> index() { return m_index; }
HTML::SerializationRecord get_referenced_value(IndexRecord const& index_record) const;
protected: protected:
explicit IDBIndex(JS::Realm&, GC::Ref<Index>, GC::Ref<IDBObjectStore>); explicit IDBIndex(JS::Realm&, GC::Ref<Index>, GC::Ref<IDBObjectStore>);
virtual void initialize(JS::Realm&) override; virtual void initialize(JS::Realm&) override;

View file

@ -419,9 +419,9 @@ WebIDL::ExceptionOr<GC::Ref<IDBRequest>> IDBObjectStore::open_cursor(JS::Value q
// 5. Let range be the result of converting a value to a key range with query. Rethrow any exceptions. // 5. Let range be the result of converting a value to a key range with query. Rethrow any exceptions.
auto range = TRY(convert_a_value_to_a_key_range(realm, query, false)); auto range = TRY(convert_a_value_to_a_key_range(realm, query, false));
// 6. Let cursor be a new cursor with its transaction set to transaction, undefined position, direction set to direction, // 6. Let cursor be a new cursor with its source handle set to this, undefined position, direction set to direction,
// got value flag set to false, undefined key and value, source set to store, range set to range, and key only flag set to false. // got value flag set to false, undefined key and value, range set to range, and key only flag set to false.
auto cursor = IDBCursor::create(realm, transaction, {}, direction, false, {}, {}, GC::Ref(*this), range, false); auto cursor = IDBCursor::create(realm, GC::Ref(*this), {}, direction, false, {}, {}, range, false);
// 7. Let operation be an algorithm to run iterate a cursor with the current Realm record and cursor. // 7. Let operation be an algorithm to run iterate a cursor with the current Realm record and cursor.
auto operation = GC::Function<WebIDL::ExceptionOr<JS::Value>()>::create(realm.heap(), [&realm, cursor] -> WebIDL::ExceptionOr<JS::Value> { auto operation = GC::Function<WebIDL::ExceptionOr<JS::Value>()>::create(realm.heap(), [&realm, cursor] -> WebIDL::ExceptionOr<JS::Value> {

View file

@ -1481,7 +1481,7 @@ WebIDL::ExceptionOr<JS::Value> retrieve_a_value_from_an_object_store(JS::Realm&
GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor, GC::Ptr<Key> key, GC::Ptr<Key> primary_key, u64 count) 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 cursors source. // 1. Let source be cursors source.
auto source = cursor->source(); auto source = cursor->internal_source();
// 2. Let direction be cursors direction. // 2. Let direction be cursors direction.
auto direction = cursor->direction(); auto direction = cursor->direction();
@ -1489,15 +1489,15 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
// 3. Assert: if primaryKey is given, source is an index and direction is "next" or "prev". // 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; auto direction_is_next_or_prev = direction == Bindings::IDBCursorDirection::Next || direction == Bindings::IDBCursorDirection::Prev;
if (primary_key) if (primary_key)
VERIFY(source.has<GC::Ref<IDBIndex>>() && direction_is_next_or_prev); VERIFY(source.has<GC::Ref<Index>>() && direction_is_next_or_prev);
// 4. Let records be the list of records in source. // 4. Let records be the list of records in source.
Variant<ReadonlySpan<Record>, ReadonlySpan<IndexRecord>> records = source.visit( Variant<ReadonlySpan<Record>, ReadonlySpan<IndexRecord>> records = source.visit(
[](GC::Ref<IDBObjectStore> object_store) -> Variant<ReadonlySpan<Record>, ReadonlySpan<IndexRecord>> { [](GC::Ref<ObjectStore> object_store) -> Variant<ReadonlySpan<Record>, ReadonlySpan<IndexRecord>> {
return object_store->store()->records(); return object_store->records();
}, },
[](GC::Ref<IDBIndex> index) -> Variant<ReadonlySpan<Record>, ReadonlySpan<IndexRecord>> { [](GC::Ref<Index> index) -> Variant<ReadonlySpan<Record>, ReadonlySpan<IndexRecord>> {
return index->index()->records(); return index->records();
}); });
// 5. Let range be cursors range. // 5. Let range be cursors range.
@ -1540,7 +1540,7 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
} }
// * If position is defined, and source is an object store, // * If position is defined, and source is an object store,
if (position && source.has<GC::Ref<IDBObjectStore>>()) { if (position && source.has<GC::Ref<ObjectStore>>()) {
auto const& inner_record = record.get<Record>(); auto const& inner_record = record.get<Record>();
// * the records key is greater than position. // * the records key is greater than position.
@ -1549,7 +1549,7 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
} }
// * If position is defined, and source is an index, // * If position is defined, and source is an index,
if (position && source.has<GC::Ref<IDBIndex>>()) { if (position && source.has<GC::Ref<Index>>()) {
auto const& inner_record = record.get<IndexRecord>(); auto const& inner_record = record.get<IndexRecord>();
// * the records key is equal to position and the records value is greater than object store position // * the records key is equal to position and the records value is greater than object store position
@ -1636,7 +1636,7 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
} }
// * If position is defined, and source is an object store, // * If position is defined, and source is an object store,
if (position && source.has<GC::Ref<IDBObjectStore>>()) { if (position && source.has<GC::Ref<ObjectStore>>()) {
auto const& inner_record = record.get<Record>(); auto const& inner_record = record.get<Record>();
// * the records key is less than position. // * the records key is less than position.
@ -1645,7 +1645,7 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
} }
// * If position is defined, and source is an index, // * If position is defined, and source is an index,
if (position && source.has<GC::Ref<IDBIndex>>()) { if (position && source.has<GC::Ref<Index>>()) {
auto const& inner_record = record.get<IndexRecord>(); auto const& inner_record = record.get<IndexRecord>();
// * the records key is equal to position and the records value is less than object store position // * the records key is equal to position and the records value is less than object store position
@ -1780,7 +1780,7 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
cursor->set_key(nullptr); cursor->set_key(nullptr);
// 2. If source is an index, set cursors object store position to undefined. // 2. If source is an index, set cursors object store position to undefined.
if (source.has<GC::Ref<IDBIndex>>()) if (source.has<GC::Ref<Index>>())
cursor->set_object_store_position(nullptr); cursor->set_object_store_position(nullptr);
// 3. If cursors key only flag is false, set cursors value to undefined. // 3. If cursors key only flag is false, set cursors value to undefined.
@ -1797,7 +1797,7 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
[](auto val) { return val.key; }); [](auto val) { return val.key; });
// 4. If source is an index, let object store position be found records value. // 4. If source is an index, let object store position be found records value.
if (source.has<GC::Ref<IDBIndex>>()) if (source.has<GC::Ref<Index>>())
object_store_position = found_record.get<IndexRecord>().value; object_store_position = found_record.get<IndexRecord>().value;
// 5. Decrease count by 1. // 5. Decrease count by 1.
@ -1808,7 +1808,7 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
cursor->set_position(position); cursor->set_position(position);
// 11. If source is an index, set cursors object store position to object store position. // 11. If source is an index, set cursors object store position to object store position.
if (source.has<GC::Ref<IDBIndex>>()) if (source.has<GC::Ref<Index>>())
cursor->set_object_store_position(object_store_position); cursor->set_object_store_position(object_store_position);
// 12. Set cursors key to found records key. // 12. Set cursors key to found records key.
@ -1821,11 +1821,11 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
// 1. Let serialized be found records value if source is an object store, or found records referenced value otherwise. // 1. Let serialized be found records value if source is an object store, or found records referenced value otherwise.
auto serialized = source.visit( auto serialized = source.visit(
[&](GC::Ref<IDBObjectStore>) { [&](GC::Ref<ObjectStore>) {
return found_record.get<Record>().value; return found_record.get<Record>().value;
}, },
[&](GC::Ref<IDBIndex> index) { [&](GC::Ref<Index> index) {
return index->get_referenced_value(found_record.get<IndexRecord>()); return index->referenced_value(found_record.get<IndexRecord>());
}); });
// 2. Set cursors value to ! StructuredDeserialize(serialized, targetRealm) // 2. Set cursors value to ! StructuredDeserialize(serialized, targetRealm)

View file

@ -57,4 +57,18 @@ bool Index::has_record_with_key(GC::Ref<Key> key)
return index != m_records.end(); return index != m_records.end();
} }
// https://w3c.github.io/IndexedDB/#index-referenced-value
HTML::SerializationRecord Index::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 indexs referenced object store which has a key equal to the indexs records value.
return m_object_store
->records()
.first_matching([&](auto const& store_record) {
return Key::equals(store_record.key, index_record.value);
})
.value()
.value;
}
} }

View file

@ -41,6 +41,8 @@ public:
[[nodiscard]] bool has_record_with_key(GC::Ref<Key> key); [[nodiscard]] bool has_record_with_key(GC::Ref<Key> key);
HTML::SerializationRecord referenced_value(IndexRecord const& index_record) const;
protected: protected:
virtual void visit_edges(Visitor&) override; virtual void visit_edges(Visitor&) override;