From 73272d92f068e9f949ac089de208a7afda871b75 Mon Sep 17 00:00:00 2001 From: stelar7 Date: Wed, 8 Jan 2025 23:25:46 +0100 Subject: [PATCH] LibWeb: Implement IDBKeyRange --- Libraries/LibWeb/IndexedDB/IDBKeyRange.cpp | 147 +++++++++++++++++- Libraries/LibWeb/IndexedDB/IDBKeyRange.h | 39 ++++- Libraries/LibWeb/IndexedDB/IDBKeyRange.idl | 18 +-- .../LibWeb/IndexedDB/Internal/Algorithms.cpp | 85 ++++++++++ .../LibWeb/IndexedDB/Internal/Algorithms.h | 1 + Libraries/LibWeb/IndexedDB/Internal/Key.h | 10 ++ 6 files changed, 286 insertions(+), 14 deletions(-) diff --git a/Libraries/LibWeb/IndexedDB/IDBKeyRange.cpp b/Libraries/LibWeb/IndexedDB/IDBKeyRange.cpp index 93294d5b6cd..86182dd0c6a 100644 --- a/Libraries/LibWeb/IndexedDB/IDBKeyRange.cpp +++ b/Libraries/LibWeb/IndexedDB/IDBKeyRange.cpp @@ -4,9 +4,12 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include +#include #include #include #include +#include namespace Web::IndexedDB { @@ -14,14 +17,18 @@ GC_DEFINE_ALLOCATOR(IDBKeyRange); IDBKeyRange::~IDBKeyRange() = default; -IDBKeyRange::IDBKeyRange(JS::Realm& realm) +IDBKeyRange::IDBKeyRange(JS::Realm& realm, GC::Ptr lower_bound, GC::Ptr upper_bound, bool lower_open, bool upper_open) : PlatformObject(realm) + , m_lower_bound(lower_bound) + , m_upper_bound(upper_bound) + , m_lower_open(lower_open) + , m_upper_open(upper_open) { } -GC::Ref IDBKeyRange::create(JS::Realm& realm) +GC::Ref IDBKeyRange::create(JS::Realm& realm, GC::Ptr lower_bound, GC::Ptr upper_bound, bool lower_open, bool upper_open) { - return realm.create(realm); + return realm.create(realm, lower_bound, upper_bound, lower_open, upper_open); } void IDBKeyRange::initialize(JS::Realm& realm) @@ -30,4 +37,138 @@ void IDBKeyRange::initialize(JS::Realm& realm) WEB_SET_PROTOTYPE_FOR_INTERFACE(IDBKeyRange); } +void IDBKeyRange::visit_edges(Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_lower_bound); + visitor.visit(m_upper_bound); +} + +// https://w3c.github.io/IndexedDB/#in +bool IDBKeyRange::is_in_range(GC::Ref key) const +{ + // A key is in a key range range if both of the following conditions are fulfilled: + + // The range’s lower bound is null, or it is less than key, or it is both equal to key and the range’s lower open flag is false. + auto lower_bound_in_range = this->lower_key() == nullptr || Key::less_than(*this->lower_key(), key) || (Key::equals(key, *this->lower_key()) && !this->lower_open()); + + // The range’s upper bound is null, or it is greater than key, or it is both equal to key and the range’s upper open flag is false. + auto upper_bound_in_range = this->upper_key() == nullptr || Key::greater_than(*this->upper_key(), key) || (Key::equals(key, *this->upper_key()) && !this->upper_open()); + + return lower_bound_in_range && upper_bound_in_range; +} + +// https://w3c.github.io/IndexedDB/#dom-idbkeyrange-only +WebIDL::ExceptionOr> IDBKeyRange::only(JS::VM& vm, JS::Value value) +{ + auto& realm = *vm.current_realm(); + + // 1. Let key be the result of converting a value to a key with value. Rethrow any exceptions. + auto maybe_key = convert_a_value_to_a_key(realm, value); + + // 2. If key is invalid, throw a "DataError" DOMException. + if (maybe_key.is_error()) + return WebIDL::DataError::create(realm, "Value is invalid"_string); + + auto key = maybe_key.release_value(); + + // 3. Create and return a new key range containing only key. + return IDBKeyRange::create(realm, key, key, false, false); +} + +// https://w3c.github.io/IndexedDB/#dom-idbkeyrange-lowerbound +WebIDL::ExceptionOr> IDBKeyRange::lower_bound(JS::VM& vm, JS::Value lower, bool open) +{ + auto& realm = *vm.current_realm(); + + // 1. Let lowerKey be the result of converting a value to a key with lower. Rethrow any exceptions. + auto lower_key = convert_a_value_to_a_key(realm, lower); + + // 2. If lowerKey is invalid, throw a "DataError" DOMException. + if (lower_key.is_error()) + return WebIDL::DataError::create(realm, "Value is invalid"_string); + + // 3. Create and return a new key range with lower bound set to lowerKey, lower open flag set to open, upper bound set to null, and upper open flag set to true. + return IDBKeyRange::create(realm, lower_key.release_value(), {}, open, true); +} + +// https://w3c.github.io/IndexedDB/#dom-idbkeyrange-upperbound +WebIDL::ExceptionOr> IDBKeyRange::upper_bound(JS::VM& vm, JS::Value upper, bool open) +{ + auto& realm = *vm.current_realm(); + + // 1. Let upperKey be the result of converting a value to a key with upper. Rethrow any exceptions. + auto upper_key = convert_a_value_to_a_key(realm, upper); + + // 2. If upperKey is invalid, throw a "DataError" DOMException. + if (upper_key.is_error()) + return WebIDL::DataError::create(realm, "Value is invalid"_string); + + // 3. Create and return a new key range with lower bound set to null, lower open flag set to true, upper bound set to upperKey, and upper open flag set to open. + return IDBKeyRange::create(realm, {}, upper_key.release_value(), true, open); +} + +// https://w3c.github.io/IndexedDB/#dom-idbkeyrange-bound +WebIDL::ExceptionOr> IDBKeyRange::bound(JS::VM& vm, JS::Value lower, JS::Value upper, bool lower_open, bool upper_open) +{ + auto& realm = *vm.current_realm(); + + // 1. Let lowerKey be the result of converting a value to a key with lower. Rethrow any exceptions. + auto lower_key = convert_a_value_to_a_key(realm, lower); + + // 2. If lowerKey is invalid, throw a "DataError" DOMException. + if (lower_key.is_error()) + return WebIDL::DataError::create(realm, "Value is invalid"_string); + + // 3. Let upperKey be the result of converting a value to a key with upper. Rethrow any exceptions. + auto upper_key = convert_a_value_to_a_key(realm, upper); + + // 4. If upperKey is invalid, throw a "DataError" DOMException. + if (upper_key.is_error()) + return WebIDL::DataError::create(realm, "Value is invalid"_string); + + // 5. If lowerKey is greater than upperKey, throw a "DataError" DOMException. + if (Key::less_than(upper_key.release_value(), lower_key.release_value())) + return WebIDL::DataError::create(realm, "Lower key is greater than upper key"_string); + + // 6. Create and return a new key range with lower bound set to lowerKey, lower open flag set to lowerOpen, upper bound set to upperKey and upper open flag set to upperOpen. + return IDBKeyRange::create(realm, lower_key.release_value(), upper_key.release_value(), lower_open, upper_open); +} + +// https://w3c.github.io/IndexedDB/#dom-idbkeyrange-includes +WebIDL::ExceptionOr IDBKeyRange::includes(JS::Value key) +{ + auto& realm = this->realm(); + + // 1. Let k be the result of converting a value to a key with key. Rethrow any exceptions. + auto k = convert_a_value_to_a_key(realm, key); + + // 2. If k is invalid, throw a "DataError" DOMException. + if (k.is_error()) + return WebIDL::DataError::create(realm, "Value is invalid"_string); + + // 3. Return true if k is in this range, and false otherwise. + return is_in_range(k.release_value()); +} + +// https://w3c.github.io/IndexedDB/#dom-idbkeyrange-lower +JS::Value IDBKeyRange::lower() const +{ + // The lower getter steps are to return the result of converting a key to a value with this's lower bound if it is not null, or undefined otherwise. + if (m_lower_bound) + return convert_a_key_to_a_value(this->realm(), *m_lower_bound); + + return JS::js_undefined(); +} + +// https://w3c.github.io/IndexedDB/#dom-idbkeyrange-upper +JS::Value IDBKeyRange::upper() const +{ + // The upper getter steps are to return the result of converting a key to a value with this's upper bound if it is not null, or undefined otherwise. + if (m_upper_bound) + return convert_a_key_to_a_value(this->realm(), *m_upper_bound); + + return JS::js_undefined(); +} + } diff --git a/Libraries/LibWeb/IndexedDB/IDBKeyRange.h b/Libraries/LibWeb/IndexedDB/IDBKeyRange.h index c512bd0f253..1d7ec778917 100644 --- a/Libraries/LibWeb/IndexedDB/IDBKeyRange.h +++ b/Libraries/LibWeb/IndexedDB/IDBKeyRange.h @@ -6,8 +6,13 @@ #pragma once +#include #include +#include +#include +#include #include +#include namespace Web::IndexedDB { @@ -18,11 +23,41 @@ class IDBKeyRange : public Bindings::PlatformObject { public: virtual ~IDBKeyRange() override; - [[nodiscard]] static GC::Ref create(JS::Realm&); + [[nodiscard]] static GC::Ref create(JS::Realm&, GC::Ptr lower_bound, GC::Ptr upper_bound, bool lower_open, bool upper_open); + + [[nodiscard]] JS::Value lower() const; + [[nodiscard]] JS::Value upper() const; + bool lower_open() const { return m_lower_open; } + bool upper_open() const { return m_upper_open; } + + static WebIDL::ExceptionOr> only(JS::VM&, JS::Value); + static WebIDL::ExceptionOr> lower_bound(JS::VM&, JS::Value, bool); + static WebIDL::ExceptionOr> upper_bound(JS::VM&, JS::Value, bool); + static WebIDL::ExceptionOr> bound(JS::VM&, JS::Value, JS::Value, bool, bool); + WebIDL::ExceptionOr includes(JS::Value); + + bool is_unbound() const { return m_lower_bound == nullptr && m_upper_bound == nullptr; } + bool is_in_range(GC::Ref) const; + GC::Ptr lower_key() const { return m_lower_bound; } + GC::Ptr upper_key() const { return m_upper_bound; } protected: - explicit IDBKeyRange(JS::Realm&); + explicit IDBKeyRange(JS::Realm&, GC::Ptr lower_bound, GC::Ptr upper_bound, bool lower_open, bool upper_open); virtual void initialize(JS::Realm&) override; + virtual void visit_edges(Visitor& visitor) override; + +private: + // A key range has an associated lower bound (null or a key). + GC::Ptr m_lower_bound; + + // A key range has an associated upper bound (null or a key). + GC::Ptr m_upper_bound; + + // A key range has an associated lower open flag. Unless otherwise stated it is false. + bool m_lower_open { false }; + + // A key range has an associated upper open flag. Unless otherwise stated it is false. + bool m_upper_open { false }; }; } diff --git a/Libraries/LibWeb/IndexedDB/IDBKeyRange.idl b/Libraries/LibWeb/IndexedDB/IDBKeyRange.idl index cd794f25e3f..ba474ba5105 100644 --- a/Libraries/LibWeb/IndexedDB/IDBKeyRange.idl +++ b/Libraries/LibWeb/IndexedDB/IDBKeyRange.idl @@ -1,14 +1,14 @@ [Exposed=(Window,Worker)] interface IDBKeyRange { - [FIXME] readonly attribute any lower; - [FIXME] readonly attribute any upper; - [FIXME] readonly attribute boolean lowerOpen; - [FIXME] readonly attribute boolean upperOpen; + readonly attribute any lower; + readonly attribute any upper; + readonly attribute boolean lowerOpen; + readonly attribute boolean upperOpen; - [FIXME, NewObject] static IDBKeyRange only(any value); - [FIXME, NewObject] static IDBKeyRange lowerBound(any lower, optional boolean open = false); - [FIXME, NewObject] static IDBKeyRange upperBound(any upper, optional boolean open = false); - [FIXME, NewObject] static IDBKeyRange bound(any lower, any upper, optional boolean lowerOpen = false, optional boolean upperOpen = false); + [NewObject] static IDBKeyRange only(any value); + [NewObject] static IDBKeyRange lowerBound(any lower, optional boolean open = false); + [NewObject] static IDBKeyRange upperBound(any upper, optional boolean open = false); + [NewObject] static IDBKeyRange bound(any lower, any upper, optional boolean lowerOpen = false, optional boolean upperOpen = false); - [FIXME] boolean includes(any key); + boolean includes(any key); }; diff --git a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp index c14fcf806c4..efe4aa2dbd7 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp +++ b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp @@ -495,4 +495,89 @@ void abort_a_transaction(IDBTransaction& transaction, GC::Ptr 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; + } + } + + VERIFY_NOT_REACHED(); +} + } diff --git a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h index c5803b906d8..7e9a1182b59 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h +++ b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h @@ -20,5 +20,6 @@ void close_a_database_connection(IDBDatabase&, bool forced = false); void upgrade_a_database(JS::Realm&, GC::Ref, u64, GC::Ref); WebIDL::ExceptionOr delete_a_database(JS::Realm&, StorageAPI::StorageKey, String, GC::Ref); void abort_a_transaction(IDBTransaction&, GC::Ptr); +JS::Value convert_a_key_to_a_value(JS::Realm&, GC::Ref); } diff --git a/Libraries/LibWeb/IndexedDB/Internal/Key.h b/Libraries/LibWeb/IndexedDB/Internal/Key.h index 288a7485ee5..aca300c2c0a 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Key.h +++ b/Libraries/LibWeb/IndexedDB/Internal/Key.h @@ -45,6 +45,16 @@ public: [[nodiscard]] KeyType type() { return m_type; } [[nodiscard]] KeyValue value() { return m_value; } + [[nodiscard]] double value_as_double() { return m_value.get(); } + [[nodiscard]] AK::String value_as_string() { return m_value.get(); } + [[nodiscard]] ByteBuffer value_as_byte_buffer() { return m_value.get(); } + [[nodiscard]] Vector> value_as_vector() { return m_value.get>>(); } + [[nodiscard]] Vector> subkeys() + { + VERIFY(m_type == Array); + return value_as_vector(); + } + [[nodiscard]] static GC::Ref create_number(JS::Realm& realm, double value) { return create(realm, Number, value); } [[nodiscard]] static GC::Ref create_date(JS::Realm& realm, double value) { return create(realm, Date, value); } [[nodiscard]] static GC::Ref create_string(JS::Realm& realm, AK::String const& value) { return create(realm, String, value); }