Files
ladybird/Libraries/LibWeb/IndexedDB/IDBIndex.cpp
Shannon Booth fd44da6829 LibWeb/Bindings: Emit one bindings header and cpp per IDL
Previously, the LibWeb bindings generator would output multiple per
interface files like Prototype/Constructor/Namespace/GlobalMixin
depending on the contents of that IDL file.

This complicates the build system as it means that it does not know
what files will be generated without knowledge of the contents of that
IDL file.

Instead, for each IDL file only generate a single Bindings/<IDLFile>.h
and Bindings/<IDLFile>.cpp.
2026-04-21 07:36:13 +02:00

323 lines
15 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Copyright (c) 2024, stelar7 <dudedbz@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/Array.h>
#include <LibWeb/Bindings/IDBIndex.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/HTML/EventNames.h>
#include <LibWeb/IndexedDB/IDBCursor.h>
#include <LibWeb/IndexedDB/IDBCursorWithValue.h>
#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)
: PlatformObject(realm)
, m_index(index)
, m_object_store_handle(object_store)
, m_name(index->name())
{
}
GC::Ref<IDBIndex> IDBIndex::create(JS::Realm& realm, GC::Ref<Index> index, GC::Ref<IDBObjectStore> object_store)
{
auto handle = realm.create<IDBIndex>(realm, index, object_store);
object_store->transaction()->register_index_handle({}, handle);
return handle;
}
void IDBIndex::initialize(JS::Realm& realm)
{
WEB_SET_PROTOTYPE_FOR_INTERFACE(IDBIndex);
Base::initialize(realm);
}
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);
// 6. If index or indexs object store has been deleted, throw an "InvalidStateError" DOMException.
if (index->is_deleted() || index->object_store()->is_deleted())
return WebIDL::InvalidStateError::create(realm, "Index or its object store has been deleted"_utf16);
// 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);
// AD-HOC: Log the rename for potential revert on abort.
m_object_store_handle->store()->mutation_log()->note_index_renamed(index, index->name());
// 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.
auto index = this->index();
// 3. If index or indexs object store has been deleted, throw an "InvalidStateError" DOMException.
if (index->is_deleted() || index->object_store()->is_deleted())
return WebIDL::InvalidStateError::create(realm, "Index or its object store has been deleted"_utf16);
// 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;
}
// 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();
// 3. If index or indexs object store has been deleted, throw an "InvalidStateError" DOMException.
if (index->is_deleted() || index->object_store()->is_deleted())
return WebIDL::InvalidStateError::create(realm, "Index or its object store has been deleted"_utf16);
// 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);
// 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;
}
// 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();
// 3. If index or indexs object store has been deleted, throw an "InvalidStateError" DOMException.
if (index->is_deleted() || index->object_store()->is_deleted())
return WebIDL::InvalidStateError::create(realm, "Index or its object store has been deleted"_utf16);
// 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);
// 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;
}
// 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)
{
// 1. Return the result of creating a request to retrieve multiple items with the current Realm record, this,
// "value", queryOrOptions, and count if given. Rethrow any exceptions.
return create_a_request_to_retrieve_multiple_items(realm(), GC::Ref(*this), RecordKind::Value, *query_or_options, count);
}
// 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)
{
// 1. Return the result of creating a request to retrieve multiple items with the current Realm record, this, "key",
// queryOrOptions, and count if given. Rethrow any exceptions.
return create_a_request_to_retrieve_multiple_items(realm(), GC::Ref(*this), RecordKind::Key, *query_or_options, count);
}
// 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();
// 3. If index or indexs object store has been deleted, throw an "InvalidStateError" DOMException.
if (index->is_deleted() || index->object_store()->is_deleted())
return WebIDL::InvalidStateError::create(realm, "Index or its object store has been deleted"_utf16);
// 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);
// 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.
auto index = this->index();
// 3. If index or indexs object store has been deleted, throw an "InvalidStateError" DOMException.
if (index->is_deleted() || index->object_store()->is_deleted())
return WebIDL::InvalidStateError::create(realm, "Index or its object store has been deleted"_utf16);
// 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)
{
// 1. Return the result of creating a request to retrieve multiple items with the current Realm record, this,
// "record", and options. Rethrow any exceptions.
auto converted_options = JS::Object::create(realm(), nullptr);
MUST(converted_options->create_data_property("query"_utf16, options.query));
MUST(converted_options->create_data_property("count"_utf16, options.count.has_value() ? JS::Value(options.count.value()) : JS::js_undefined()));
MUST(converted_options->create_data_property("direction"_utf16, JS::PrimitiveString::create(realm().vm(), idl_enum_to_string(options.direction))));
return create_a_request_to_retrieve_multiple_items(realm(), GC::Ref(*this), RecordKind::Record, converted_options, {});
}
}