ladybird/Libraries/LibWeb/IndexedDB/IDBIndex.cpp

439 lines
20 KiB
C++
Raw Normal View History

2024-11-07 18:52:59 +01:00
/*
* Copyright (c) 2024, stelar7 <dudedbz@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/Array.h>
2024-11-07 18:52:59 +01:00
#include <LibWeb/Bindings/IDBIndexPrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/HTML/EventNames.h>
#include <LibWeb/IndexedDB/IDBCursor.h>
#include <LibWeb/IndexedDB/IDBCursorWithValue.h>
2024-11-07 18:52:59 +01:00
#include <LibWeb/IndexedDB/IDBIndex.h>
namespace Web::IndexedDB {
GC_DEFINE_ALLOCATOR(IDBIndex);
IDBIndex::~IDBIndex() = default;
IDBIndex::IDBIndex(JS::Realm& realm, GC::Ref<Index> index, GC::Ref<IDBObjectStore> object_store)
2024-11-07 18:52:59 +01:00
: PlatformObject(realm)
, m_index(index)
, m_object_store_handle(object_store)
, m_name(index->name())
2024-11-07 18:52:59 +01:00
{
}
GC::Ref<IDBIndex> IDBIndex::create(JS::Realm& realm, GC::Ref<Index> index, GC::Ref<IDBObjectStore> object_store)
2024-11-07 18:52:59 +01:00
{
return realm.create<IDBIndex>(realm, index, object_store);
2024-11-07 18:52:59 +01:00
}
void IDBIndex::initialize(JS::Realm& realm)
{
WEB_SET_PROTOTYPE_FOR_INTERFACE(IDBIndex);
Base::initialize(realm);
2024-11-07 18:52:59 +01:00
}
void IDBIndex::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_index);
visitor.visit(m_object_store_handle);
}
// https://w3c.github.io/IndexedDB/#dom-idbindex-name
WebIDL::ExceptionOr<void> IDBIndex::set_name(String const& value)
{
auto& realm = this->realm();
// 1. Let name be the given value.
auto const& name = value;
// 2. Let transaction be thiss transaction.
auto transaction = this->transaction();
// 3. Let index be thiss index.
auto index = this->index();
// 4. If transaction is not an upgrade transaction, throw an "InvalidStateError" DOMException.
if (!transaction->is_upgrade_transaction())
return WebIDL::InvalidStateError::create(realm, "Transaction is not an upgrade transaction"_utf16);
// 5. If transactions state is not active, then throw a "TransactionInactiveError" DOMException.
if (!transaction->is_active())
return WebIDL::TransactionInactiveError::create(realm, "Transaction is not active while updating index name"_utf16);
// FIXME: 6. If index or indexs object store has been deleted, throw an "InvalidStateError" DOMException.
// 7. If indexs name is equal to name, terminate these steps.
if (index->name() == name)
return {};
// 8. If an index named name already exists in indexs object store, throw a "ConstraintError" DOMException.
if (index->object_store()->index_set().contains(name))
return WebIDL::ConstraintError::create(realm, "An index with the given name already exists"_utf16);
// 9. Set indexs name to name.
index->set_name(name);
// NOTE: Update the key in the map so it still matches the name
auto old_value = m_object_store_handle->index_set().take(m_name).release_value();
m_object_store_handle->index_set().set(name, old_value);
// 10. Set thiss name to name.
m_name = name;
return {};
}
// https://w3c.github.io/IndexedDB/#dom-idbindex-keypath
JS::Value IDBIndex::key_path() const
{
return m_index->key_path().visit(
[&](String const& value) -> JS::Value {
return JS::PrimitiveString::create(realm().vm(), value);
},
[&](Vector<String> const& value) -> JS::Value {
return JS::Array::create_from<String>(realm(), value.span(), [&](auto const& entry) -> JS::Value {
return JS::PrimitiveString::create(realm().vm(), entry);
});
});
}
// https://w3c.github.io/IndexedDB/#dom-idbindex-opencursor
WebIDL::ExceptionOr<GC::Ref<IDBRequest>> IDBIndex::open_cursor(JS::Value query, Bindings::IDBCursorDirection direction)
{
auto& realm = this->realm();
// 1. Let transaction be thiss transaction.
auto transaction = this->transaction();
// 2. Let index be thiss index.
[[maybe_unused]] auto index = this->index();
// FIXME: 3. If index or indexs object store has been deleted, throw an "InvalidStateError" DOMException.
// 4. If transactions state is not active, then throw a "TransactionInactiveError" DOMException.
if (!transaction->is_active())
return WebIDL::TransactionInactiveError::create(realm, "Transaction is not active while opening cursor"_utf16);
// 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));
// 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, range set to range, and key only flag set to false.
auto cursor = IDBCursor::create(realm, GC::Ref(*this), {}, direction, IDBCursor::GotValue::No, {}, {}, range, IDBCursor::KeyOnly::No);
// 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> {
return WebIDL::ExceptionOr<JS::Value>(iterate_a_cursor(realm, cursor));
});
// 8. Let request be the result of running asynchronously execute a request with this and operation.
auto request = asynchronously_execute_a_request(realm, GC::Ref(*this), operation);
// 9. Set cursors request to request.
cursor->set_request(request);
// 10. Return request.
return request;
}
2025-05-13 22:18:14 +02:00
// https://w3c.github.io/IndexedDB/#dom-idbindex-get
WebIDL::ExceptionOr<GC::Ref<IDBRequest>> IDBIndex::get(JS::Value query)
{
auto& realm = this->realm();
// 1. Let transaction be thiss transaction.
auto transaction = this->transaction();
// 2. Let index be thiss index.
auto index = this->index();
// FIXME: 3. If index or indexs object store has been deleted, throw an "InvalidStateError" DOMException.
// 4. If transactions state is not active, then throw a "TransactionInactiveError" DOMException.
if (!transaction->is_active())
return WebIDL::TransactionInactiveError::create(realm, "Transaction is not active while getting"_utf16);
2025-05-13 22:18:14 +02:00
// 5. Let range be the result of converting a value to a key range with query and true. Rethrow any exceptions.
auto range = TRY(convert_a_value_to_a_key_range(realm, query, true));
// 6. Let operation be an algorithm to run retrieve a referenced value from an index with the current Realm record, index, and range.
auto operation = GC::Function<WebIDL::ExceptionOr<JS::Value>()>::create(realm.heap(), [&realm, index, range] -> WebIDL::ExceptionOr<JS::Value> {
return retrieve_a_referenced_value_from_an_index(realm, index, range);
});
// 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
auto result = asynchronously_execute_a_request(realm, GC::Ref(*this), operation);
dbgln_if(IDB_DEBUG, "Executing request for get with uuid {}", result->uuid());
return result;
}
2025-05-13 22:21:57 +02:00
// https://w3c.github.io/IndexedDB/#dom-idbindex-getkey
WebIDL::ExceptionOr<GC::Ref<IDBRequest>> IDBIndex::get_key(JS::Value query)
{
auto& realm = this->realm();
// 1. Let transaction be thiss transaction.
auto transaction = this->transaction();
// 2. Let index be thiss index.
auto index = this->index();
// FIXME: 3. If index or indexs object store has been deleted, throw an "InvalidStateError" DOMException.
// 4. If transactions state is not active, then throw a "TransactionInactiveError" DOMException.
if (!transaction->is_active())
return WebIDL::TransactionInactiveError::create(realm, "Transaction is not active while getting key"_utf16);
2025-05-13 22:21:57 +02:00
// 5. Let range be the result of converting a value to a key range with query and true. Rethrow any exceptions.
auto range = TRY(convert_a_value_to_a_key_range(realm, query, true));
// 6. Let operation be an algorithm to run retrieve a value from an index with index and range.
auto operation = GC::Function<WebIDL::ExceptionOr<JS::Value>()>::create(realm.heap(), [&realm, index, range] -> WebIDL::ExceptionOr<JS::Value> {
return retrieve_a_value_from_an_index(realm, index, range);
});
// 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
auto result = asynchronously_execute_a_request(realm, GC::Ref(*this), operation);
dbgln_if(IDB_DEBUG, "Executing request for get key with uuid {}", result->uuid());
return result;
}
2025-05-13 22:41:18 +02:00
// https://w3c.github.io/IndexedDB/#dom-idbindex-getall
WebIDL::ExceptionOr<GC::Ref<IDBRequest>> IDBIndex::get_all(Optional<JS::Value> query_or_options, Optional<WebIDL::UnsignedLong> count)
2025-05-13 22:41:18 +02:00
{
auto& realm = this->realm();
auto& vm = realm.vm();
2025-05-13 22:41:18 +02:00
// 1. Let transaction be thiss transaction.
auto transaction = this->transaction();
// 2. Let index be thiss index.
auto index = this->index();
// FIXME: 3. If index or indexs object store has been deleted, throw an "InvalidStateError" DOMException.
// 4. If transactions state is not active, then throw a "TransactionInactiveError" DOMException.
if (!transaction->is_active())
return WebIDL::TransactionInactiveError::create(realm, "Transaction is not active while getting all"_utf16);
2025-05-13 22:41:18 +02:00
// 5. Let range be a key range.
GC::Ptr<IDBKeyRange> range;
// 6. Let direction be a cursor direction.
Bindings::IDBCursorDirection direction = Bindings::IDBCursorDirection::Next;
// 7. If running is a potentially valid key range with query_or_options is true, then:
if (is_a_potentially_valid_key_range(realm, *query_or_options)) {
// 1. Set range to the result of converting a value to a key range with query_or_options. Rethrow any exceptions.
range = TRY(convert_a_value_to_a_key_range(realm, query_or_options));
// 2. Set direction to "next".
direction = Bindings::IDBCursorDirection::Next;
}
// 8. Else:
else {
// 1. Set range to the result of converting a value to a key range with query_or_options["query"]. Rethrow any exceptions.
range = TRY(convert_a_value_to_a_key_range(realm, TRY(query_or_options->get(vm, "query"_utf16))));
// 2. Set count to query_or_options["count"].
count = TRY(TRY(query_or_options->get(vm, "count"_utf16)).to_u32(vm));
// 3. Set direction to query_or_options["direction"].
auto direction_value = TRY(TRY(query_or_options->get(vm, "direction"_utf16)).to_string(vm));
if (direction_value == "next"_string) {
direction = Bindings::IDBCursorDirection::Next;
} else if (direction_value == "nextunique"_string) {
direction = Bindings::IDBCursorDirection::Nextunique;
} else if (direction_value == "prev"_string) {
direction = Bindings::IDBCursorDirection::Prev;
} else if (direction_value == "prevunique"_string) {
direction = Bindings::IDBCursorDirection::Prevunique;
} else {
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Invalid direction value"_string };
}
}
// 9. Let operation be an algorithm to run retrieve multiple items from an index with the current Realm record, index, range, "value", direction and count if given.
auto operation = GC::Function<WebIDL::ExceptionOr<JS::Value>()>::create(realm.heap(), [&realm, index, range, direction, count] -> WebIDL::ExceptionOr<JS::Value> {
return retrieve_multiple_items_from_an_index(realm, index, GC::Ref(*range), RecordKind::Value, direction, count);
2025-05-13 22:41:18 +02:00
});
// 10. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
2025-05-13 22:41:18 +02:00
auto result = asynchronously_execute_a_request(realm, GC::Ref(*this), operation);
dbgln_if(IDB_DEBUG, "Executing request for get all with uuid {}", result->uuid());
return result;
}
// https://w3c.github.io/IndexedDB/#dom-idbindex-getallkeys
WebIDL::ExceptionOr<GC::Ref<IDBRequest>> IDBIndex::get_all_keys(Optional<JS::Value> query_or_options, Optional<WebIDL::UnsignedLong> count)
{
auto& realm = this->realm();
auto& vm = realm.vm();
// 1. Let transaction be thiss transaction.
auto transaction = this->transaction();
// 2. Let index be thiss index.
auto index = this->index();
// FIXME: 3. If index or indexs object store has been deleted, throw an "InvalidStateError" DOMException.
// 4. If transactions state is not active, then throw a "TransactionInactiveError" DOMException.
if (!transaction->is_active())
return WebIDL::TransactionInactiveError::create(realm, "Transaction is not active while getting all keys"_utf16);
// 5. Let range be a key range.
GC::Ptr<IDBKeyRange> range;
// 6. Let direction be a cursor direction.
Bindings::IDBCursorDirection direction = Bindings::IDBCursorDirection::Next;
// 7. If running is a potentially valid key range with query_or_options is true, then:
if (is_a_potentially_valid_key_range(realm, *query_or_options)) {
// 1. Set range to the result of converting a value to a key range with query_or_options. Rethrow any exceptions.
range = TRY(convert_a_value_to_a_key_range(realm, query_or_options));
// 2. Set direction to "next".
direction = Bindings::IDBCursorDirection::Next;
}
// 8. Else:
else {
// 1. Set range to the result of converting a value to a key range with query_or_options["query"]. Rethrow any exceptions.
range = TRY(convert_a_value_to_a_key_range(realm, TRY(query_or_options->get(vm, "query"_utf16))));
// 2. Set count to query_or_options["count"].
count = TRY(TRY(query_or_options->get(vm, "count"_utf16)).to_u32(vm));
// 3. Set direction to query_or_options["direction"].
auto direction_value = TRY(TRY(query_or_options->get(vm, "direction"_utf16)).to_string(vm));
if (direction_value == "next"_string) {
direction = Bindings::IDBCursorDirection::Next;
} else if (direction_value == "nextunique"_string) {
direction = Bindings::IDBCursorDirection::Nextunique;
} else if (direction_value == "prev"_string) {
direction = Bindings::IDBCursorDirection::Prev;
} else if (direction_value == "prevunique"_string) {
direction = Bindings::IDBCursorDirection::Prevunique;
} else {
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Invalid direction value"_string };
}
}
// 9. Let operation be an algorithm to run retrieve multiple items from an index with the current Realm record, index, range, "key", direction and count if given.
auto operation = GC::Function<WebIDL::ExceptionOr<JS::Value>()>::create(realm.heap(), [&realm, index, range, direction, count] -> WebIDL::ExceptionOr<JS::Value> {
return retrieve_multiple_items_from_an_index(realm, index, GC::Ref(*range), RecordKind::Key, direction, count);
});
// 10. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
auto result = asynchronously_execute_a_request(realm, GC::Ref(*this), operation);
dbgln_if(IDB_DEBUG, "Executing request for get all keys with uuid {}", result->uuid());
return result;
}
2025-05-13 22:54:08 +02:00
// https://w3c.github.io/IndexedDB/#dom-idbindex-count
WebIDL::ExceptionOr<GC::Ref<IDBRequest>> IDBIndex::count(JS::Value query)
{
auto& realm = this->realm();
// 1. Let transaction be thiss transaction.
auto transaction = this->transaction();
// 2. Let index be thiss index.
auto index = this->index();
// FIXME: 3. If index or indexs object store has been deleted, throw an "InvalidStateError" DOMException.
// 4. If transactions state is not active, then throw a "TransactionInactiveError" DOMException.
if (!transaction->is_active())
return WebIDL::TransactionInactiveError::create(realm, "Transaction is not active while counting"_utf16);
2025-05-13 22:54:08 +02:00
// 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));
// 6. Let operation be an algorithm to run count the records in a range with index and range.
auto operation = GC::Function<WebIDL::ExceptionOr<JS::Value>()>::create(realm.heap(), [index, range] -> WebIDL::ExceptionOr<JS::Value> {
return count_the_records_in_a_range(index, range);
});
// 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
auto result = asynchronously_execute_a_request(realm, GC::Ref(*this), operation);
dbgln_if(IDB_DEBUG, "Executing request for count with uuid {}", result->uuid());
return result;
}
// https://w3c.github.io/IndexedDB/#dom-idbindex-openkeycursor
WebIDL::ExceptionOr<GC::Ref<IDBRequest>> IDBIndex::open_key_cursor(JS::Value query, Bindings::IDBCursorDirection direction)
{
auto& realm = this->realm();
// 1. Let transaction be thiss transaction.
auto transaction = this->transaction();
// 2. Let index be thiss index.
[[maybe_unused]] auto index = this->index();
// FIXME: 3. If index or indexs object store has been deleted, throw an "InvalidStateError" DOMException.
// 4. If transactions state is not active, then throw a "TransactionInactiveError" DOMException.
if (!transaction->is_active())
return WebIDL::TransactionInactiveError::create(realm, "Transaction is not active while opening key cursor"_utf16);
// 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));
// 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, range set to range, and key only flag set to true.
auto cursor = IDBCursor::create(realm, GC::Ref(*this), {}, direction, IDBCursor::GotValue::No, {}, {}, range, IDBCursor::KeyOnly::Yes);
// 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> {
return WebIDL::ExceptionOr<JS::Value>(iterate_a_cursor(realm, cursor));
});
// 8. Let request be the result of running asynchronously execute a request with this and operation.
auto request = asynchronously_execute_a_request(realm, GC::Ref(*this), operation);
dbgln_if(IDB_DEBUG, "Executing request for open key cursor with uuid {}", request->uuid());
// 9. Set cursors request to request.
cursor->set_request(request);
// 10. Return request.
return request;
}
WebIDL::ExceptionOr<GC::Ref<IDBRequest>> IDBIndex::get_all_records(IDBGetAllOptions const& options)
{
auto& realm = this->realm();
// 1. Let transaction be thiss transaction.
auto transaction = this->transaction();
// 2. Let index be thiss index.
auto index = this->index();
// FIXME: 3. If index or indexs object store has been deleted, then throw an "InvalidStateError" DOMException.
// 4. If transactions state is not active, then throw a "TransactionInactiveError" DOMException.
if (!transaction->is_active())
return WebIDL::TransactionInactiveError::create(realm, "Transaction is not active while getting all records"_utf16);
// 5. Let range be the result of converting a value to a key range with options["query"]. Rethrow any exceptions.
auto range = TRY(convert_a_value_to_a_key_range(realm, options.query));
// 6. Let operation be an algorithm to run retrieve multiple items from an index with the current Realm record, index, range, "record", options["direction"], and options["count"] if given.
auto operation = GC::Function<WebIDL::ExceptionOr<JS::Value>()>::create(realm.heap(), [&realm, index, range, options]() -> WebIDL::ExceptionOr<JS::Value> {
return retrieve_multiple_items_from_an_index(realm, index, GC::Ref(*range), RecordKind::Record, options.direction, options.count);
});
// 7. Return the result (an IDBRequest) of running asynchronously execute a request with this and operation.
return asynchronously_execute_a_request(realm, GC::Ref(*this), operation);
}
2024-11-07 18:52:59 +01:00
}