Skip to content

LibWeb/IDB: Handle cursor iteration more correctly#6628

Merged
trflynn89 merged 1 commit intoLadybirdBrowser:masterfrom
stelar7:idb-cursor-iteration-direction
Nov 13, 2025
Merged

LibWeb/IDB: Handle cursor iteration more correctly#6628
trflynn89 merged 1 commit intoLadybirdBrowser:masterfrom
stelar7:idb-cursor-iteration-direction

Conversation

@stelar7
Copy link
Copy Markdown
Contributor

@stelar7 stelar7 commented Oct 29, 2025

Gives a few more WPT passes

Related spec bug: w3c/IndexedDB#481

@stelar7 stelar7 force-pushed the idb-cursor-iteration-direction branch 3 times, most recently from 81dbe72 to 53b282f Compare October 29, 2025 12:44
if (!Key::greater_than(inner_record.key, *key))
return false;
// * If the record’s key is equal to key:
if (Key::equals(inner_record.key, *key)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about the retain_satisfaction I find this harder to read and the code doesn't short circuit there anymore

Seems like you could just keep the old style with the changes?

if (Key::equals(inner_record.key, *key)) {
    if (!(Key::greater_than(inner_record.value, *primary_key) || Key::equals(inner_record.value, *primary_key)))
        return false;
} else {
    if (!Key::greater_than(inner_record.key, *key))
        return false;
}

Copy link
Copy Markdown
Contributor Author

@stelar7 stelar7 Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inversion of the conditions made matching the logic and spec comments harder to follow imo
Since it reads as "If this, do that", but the code say "If not this, do that"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree retain_satisfaction is quite awkward. It looks like a strange way to avoid using the &= operator. This also looks less efficient in that we no longer return early upon failure.

You've also changed the copied spec text in a way that makes it harder to compare to the spec itself, IMO.

Does the following not suffice?

diff --git a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp
index 16ed5c6815..b69beca703 100644
--- a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp
+++ b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp
@@ -1602,7 +1602,7 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
             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);
+                    return Key::greater_than_or_equal(inner_record.key, *key);
                 });

             if (!is_greater_than_or_equal)
@@ -1614,12 +1614,14 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
             auto const& inner_record = record.get<IndexRecord>();

             // * 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;
-
+            if (Key::equals(inner_record.key, *key)) {
+                if (!Key::greater_than_or_equal(inner_record.value, *primary_key))
+                    return false;
+            }
             // * The record’s key is greater than key.
-            if (!Key::greater_than(inner_record.key, *key))
+            else if (!Key::greater_than(inner_record.key, *key)) {
                 return false;
+            }
         }

         // * If position is defined and source is an object store:
@@ -1636,12 +1638,14 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
             auto const& inner_record = record.get<IndexRecord>();

             // * 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;
-
+            if (Key::equals(inner_record.key, *position)) {
+                if (!Key::greater_than(inner_record.value, *object_store_position))
+                    return false;
+            }
             // * The record’s key is greater than position.
-            if (!Key::greater_than(inner_record.key, *position))
+            else if (!Key::greater_than(inner_record.key, *position)) {
                 return false;
+            }
         }

         // * The record’s key is in range.
@@ -1661,7 +1665,7 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
             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);
+                    return Key::greater_than_or_equal(inner_record.key, *key);
                 });

             if (!is_greater_than_or_equal)
@@ -1674,7 +1678,7 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
             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);
+                    return Key::greater_than(inner_record.key, *position);
                 });

             if (!is_greater_than_position)
@@ -1698,7 +1702,7 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
             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);
+                    return Key::less_than_or_equal(inner_record.key, *key);
                 });

             if (!is_less_than_or_equal)
@@ -1710,12 +1714,14 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
             auto const& inner_record = record.get<IndexRecord>();

             // * 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;
-
+            if (Key::equals(inner_record.key, *key)) {
+                if (!Key::less_than_or_equal(inner_record.value, *primary_key))
+                    return false;
+            }
             // * The record’s key is less than key.
-            if (!Key::less_than(inner_record.key, *key))
+            else if (!Key::less_than(inner_record.key, *key)) {
                 return false;
+            }
         }

         // * If position is defined and source is an object store:
@@ -1732,12 +1738,14 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
             auto const& inner_record = record.get<IndexRecord>();

             // * 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;
-
+            if (Key::equals(inner_record.key, *position)) {
+                if (!Key::less_than(inner_record.value, *object_store_position))
+                    return false;
+            }
             // * The record’s key is less than position.
-            if (!Key::less_than(inner_record.key, *position))
+            else if (!Key::less_than(inner_record.key, *position)) {
                 return false;
+            }
         }

         // * The record’s key is in range.
@@ -1757,7 +1765,7 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
             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);
+                    return Key::less_than_or_equal(inner_record.key, *key);
                 });

             if (!is_less_than_or_equal)
@@ -1770,7 +1778,7 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
             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);
+                    return Key::less_than(inner_record.key, *position);
                 });

             if (!is_less_than_position)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree retain_satisfaction is quite awkward. It looks like a strange way to avoid using the &= operator.

I forgot that &= existed tbh 😅

You've also changed the copied spec text in a way that makes it harder to compare to the spec itself, IMO.

It depends on the outcome of the linked spec PR. For now its matching the wording in that PR.

This also looks less efficient in that we no longer return early upon failure.

Ive restored the early return (and imo. improved readability) with a macro now

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ive restored the early return (and imo. improved readability) with a macro now

This does not feel like an improvement to me, a macro just adds a level of indirection here when reading this code. Let's not be unnecessarily fancy, I would much prefer just a bog standard:

if (!condition)
    return false;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Macro removed 👍

if (!Key::greater_than(inner_record.key, *key))
return false;
// * If the record’s key is equal to key:
if (Key::equals(inner_record.key, *key)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree retain_satisfaction is quite awkward. It looks like a strange way to avoid using the &= operator. This also looks less efficient in that we no longer return early upon failure.

You've also changed the copied spec text in a way that makes it harder to compare to the spec itself, IMO.

Does the following not suffice?

diff --git a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp
index 16ed5c6815..b69beca703 100644
--- a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp
+++ b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp
@@ -1602,7 +1602,7 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
             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);
+                    return Key::greater_than_or_equal(inner_record.key, *key);
                 });

             if (!is_greater_than_or_equal)
@@ -1614,12 +1614,14 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
             auto const& inner_record = record.get<IndexRecord>();

             // * 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;
-
+            if (Key::equals(inner_record.key, *key)) {
+                if (!Key::greater_than_or_equal(inner_record.value, *primary_key))
+                    return false;
+            }
             // * The record’s key is greater than key.
-            if (!Key::greater_than(inner_record.key, *key))
+            else if (!Key::greater_than(inner_record.key, *key)) {
                 return false;
+            }
         }

         // * If position is defined and source is an object store:
@@ -1636,12 +1638,14 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
             auto const& inner_record = record.get<IndexRecord>();

             // * 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;
-
+            if (Key::equals(inner_record.key, *position)) {
+                if (!Key::greater_than(inner_record.value, *object_store_position))
+                    return false;
+            }
             // * The record’s key is greater than position.
-            if (!Key::greater_than(inner_record.key, *position))
+            else if (!Key::greater_than(inner_record.key, *position)) {
                 return false;
+            }
         }

         // * The record’s key is in range.
@@ -1661,7 +1665,7 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
             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);
+                    return Key::greater_than_or_equal(inner_record.key, *key);
                 });

             if (!is_greater_than_or_equal)
@@ -1674,7 +1678,7 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
             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);
+                    return Key::greater_than(inner_record.key, *position);
                 });

             if (!is_greater_than_position)
@@ -1698,7 +1702,7 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
             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);
+                    return Key::less_than_or_equal(inner_record.key, *key);
                 });

             if (!is_less_than_or_equal)
@@ -1710,12 +1714,14 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
             auto const& inner_record = record.get<IndexRecord>();

             // * 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;
-
+            if (Key::equals(inner_record.key, *key)) {
+                if (!Key::less_than_or_equal(inner_record.value, *primary_key))
+                    return false;
+            }
             // * The record’s key is less than key.
-            if (!Key::less_than(inner_record.key, *key))
+            else if (!Key::less_than(inner_record.key, *key)) {
                 return false;
+            }
         }

         // * If position is defined and source is an object store:
@@ -1732,12 +1738,14 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
             auto const& inner_record = record.get<IndexRecord>();

             // * 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;
-
+            if (Key::equals(inner_record.key, *position)) {
+                if (!Key::less_than(inner_record.value, *object_store_position))
+                    return false;
+            }
             // * The record’s key is less than position.
-            if (!Key::less_than(inner_record.key, *position))
+            else if (!Key::less_than(inner_record.key, *position)) {
                 return false;
+            }
         }

         // * The record’s key is in range.
@@ -1757,7 +1765,7 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
             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);
+                    return Key::less_than_or_equal(inner_record.key, *key);
                 });

             if (!is_less_than_or_equal)
@@ -1770,7 +1778,7 @@ GC::Ptr<IDBCursor> iterate_a_cursor(JS::Realm& realm, GC::Ref<IDBCursor> cursor,
             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);
+                    return Key::less_than(inner_record.key, *position);
                 });

             if (!is_less_than_position)

Comment thread Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp Outdated
@stelar7 stelar7 force-pushed the idb-cursor-iteration-direction branch 2 times, most recently from bbf9826 to fe26907 Compare November 11, 2025 21:14
@stelar7 stelar7 force-pushed the idb-cursor-iteration-direction branch from fe26907 to 03ffb8d Compare November 11, 2025 21:16
@trflynn89 trflynn89 merged commit 684c543 into LadybirdBrowser:master Nov 13, 2025
22 of 25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants