mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-20 03:25:13 +00:00
LibWeb: Implement IDBKeyRange
This commit is contained in:
parent
596f1d8426
commit
73272d92f0
Notes:
github-actions[bot]
2025-01-14 22:47:28 +00:00
Author: https://github.com/stelar7 Commit: https://github.com/LadybirdBrowser/ladybird/commit/73272d92f06 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3190 Reviewed-by: https://github.com/Lubrsi Reviewed-by: https://github.com/gmta ✅
6 changed files with 286 additions and 14 deletions
|
@ -4,9 +4,12 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Optional.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
#include <LibWeb/Bindings/IDBKeyRangePrototype.h>
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/IndexedDB/IDBKeyRange.h>
|
||||
#include <LibWeb/IndexedDB/Internal/Algorithms.h>
|
||||
|
||||
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<Key> lower_bound, GC::Ptr<Key> 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> IDBKeyRange::create(JS::Realm& realm)
|
||||
GC::Ref<IDBKeyRange> IDBKeyRange::create(JS::Realm& realm, GC::Ptr<Key> lower_bound, GC::Ptr<Key> upper_bound, bool lower_open, bool upper_open)
|
||||
{
|
||||
return realm.create<IDBKeyRange>(realm);
|
||||
return realm.create<IDBKeyRange>(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> 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<GC::Ref<IDBKeyRange>> 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<GC::Ref<IDBKeyRange>> 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<GC::Ref<IDBKeyRange>> 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<GC::Ref<IDBKeyRange>> 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<bool> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,8 +6,13 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Types.h>
|
||||
#include <LibGC/Heap.h>
|
||||
#include <LibGC/Ptr.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
#include <LibWeb/Bindings/PlatformObject.h>
|
||||
#include <LibWeb/IndexedDB/Internal/Key.h>
|
||||
|
||||
namespace Web::IndexedDB {
|
||||
|
||||
|
@ -18,11 +23,41 @@ class IDBKeyRange : public Bindings::PlatformObject {
|
|||
|
||||
public:
|
||||
virtual ~IDBKeyRange() override;
|
||||
[[nodiscard]] static GC::Ref<IDBKeyRange> create(JS::Realm&);
|
||||
[[nodiscard]] static GC::Ref<IDBKeyRange> create(JS::Realm&, GC::Ptr<Key> lower_bound, GC::Ptr<Key> 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<GC::Ref<IDBKeyRange>> only(JS::VM&, JS::Value);
|
||||
static WebIDL::ExceptionOr<GC::Ref<IDBKeyRange>> lower_bound(JS::VM&, JS::Value, bool);
|
||||
static WebIDL::ExceptionOr<GC::Ref<IDBKeyRange>> upper_bound(JS::VM&, JS::Value, bool);
|
||||
static WebIDL::ExceptionOr<GC::Ref<IDBKeyRange>> bound(JS::VM&, JS::Value, JS::Value, bool, bool);
|
||||
WebIDL::ExceptionOr<bool> includes(JS::Value);
|
||||
|
||||
bool is_unbound() const { return m_lower_bound == nullptr && m_upper_bound == nullptr; }
|
||||
bool is_in_range(GC::Ref<Key>) const;
|
||||
GC::Ptr<Key> lower_key() const { return m_lower_bound; }
|
||||
GC::Ptr<Key> upper_key() const { return m_upper_bound; }
|
||||
|
||||
protected:
|
||||
explicit IDBKeyRange(JS::Realm&);
|
||||
explicit IDBKeyRange(JS::Realm&, GC::Ptr<Key> lower_bound, GC::Ptr<Key> 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<Key> m_lower_bound;
|
||||
|
||||
// A key range has an associated upper bound (null or a key).
|
||||
GC::Ptr<Key> 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 };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -495,4 +495,89 @@ void abort_a_transaction(IDBTransaction& transaction, GC::Ptr<WebIDL::DOMExcepti
|
|||
}));
|
||||
}
|
||||
|
||||
// https://w3c.github.io/IndexedDB/#convert-a-key-to-a-value
|
||||
JS::Value convert_a_key_to_a_value(JS::Realm& realm, GC::Ref<Key> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,5 +20,6 @@ void close_a_database_connection(IDBDatabase&, bool forced = false);
|
|||
void upgrade_a_database(JS::Realm&, GC::Ref<IDBDatabase>, u64, GC::Ref<IDBRequest>);
|
||||
WebIDL::ExceptionOr<u64> delete_a_database(JS::Realm&, StorageAPI::StorageKey, String, GC::Ref<IDBRequest>);
|
||||
void abort_a_transaction(IDBTransaction&, GC::Ptr<WebIDL::DOMException>);
|
||||
JS::Value convert_a_key_to_a_value(JS::Realm&, GC::Ref<Key>);
|
||||
|
||||
}
|
||||
|
|
|
@ -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<double>(); }
|
||||
[[nodiscard]] AK::String value_as_string() { return m_value.get<AK::String>(); }
|
||||
[[nodiscard]] ByteBuffer value_as_byte_buffer() { return m_value.get<ByteBuffer>(); }
|
||||
[[nodiscard]] Vector<GC::Root<Key>> value_as_vector() { return m_value.get<Vector<GC::Root<Key>>>(); }
|
||||
[[nodiscard]] Vector<GC::Root<Key>> subkeys()
|
||||
{
|
||||
VERIFY(m_type == Array);
|
||||
return value_as_vector();
|
||||
}
|
||||
|
||||
[[nodiscard]] static GC::Ref<Key> create_number(JS::Realm& realm, double value) { return create(realm, Number, value); }
|
||||
[[nodiscard]] static GC::Ref<Key> create_date(JS::Realm& realm, double value) { return create(realm, Date, value); }
|
||||
[[nodiscard]] static GC::Ref<Key> create_string(JS::Realm& realm, AK::String const& value) { return create(realm, String, value); }
|
||||
|
|
Loading…
Add table
Reference in a new issue