LibWeb/IDB: Implement evaluate_key_path_on_a_value

This commit is contained in:
stelar7 2025-04-11 10:51:03 +02:00
parent 5ad3db38aa
commit 75fa32d597
2 changed files with 122 additions and 0 deletions

View file

@ -14,6 +14,8 @@
#include <LibJS/Runtime/TypedArray.h>
#include <LibJS/Runtime/VM.h>
#include <LibWeb/DOM/EventDispatcher.h>
#include <LibWeb/FileAPI/Blob.h>
#include <LibWeb/FileAPI/File.h>
#include <LibWeb/HTML/EventNames.h>
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
#include <LibWeb/HTML/StructuredSerialize.h>
@ -855,4 +857,123 @@ WebIDL::ExceptionOr<ErrorOr<GC::Ref<Key>>> convert_a_value_to_a_multi_entry_key(
return convert_a_value_to_a_key(realm, value);
}
// https://w3c.github.io/IndexedDB/#evaluate-a-key-path-on-a-value
WebIDL::ExceptionOr<ErrorOr<JS::Value>> evaluate_key_path_on_a_value(JS::Realm& realm, JS::Value value, KeyPath const& key_path)
{
// 1. If keyPath is a list of strings, then:
if (key_path.has<Vector<String>>()) {
auto const& key_path_list = key_path.get<Vector<String>>();
// 1. Let result be a new Array object created as if by the expression [].
auto result = MUST(JS::Array::create(realm, 0));
// 2. Let i be 0.
u64 i = 0;
// 3. For each item of keyPath:
for (auto const& item : key_path_list) {
// 1. Let key be the result of recursively evaluating a key path on a value with item and value.
auto completion_key = evaluate_key_path_on_a_value(realm, value, item);
// 2. Assert: key is not an abrupt completion.
VERIFY(!completion_key.is_error());
// 3. If key is failure, abort the overall algorithm and return failure.
auto key = TRY(TRY(completion_key));
// 4. Let p be ! ToString(i).
auto p = JS::PropertyKey { i };
// 5. Let status be CreateDataProperty(result, p, key).
auto status = MUST(result->create_data_property(p, key));
// 6. Assert: status is true.
VERIFY(status);
// 7. Increase i by 1.
i++;
}
// 4. Return result.
return result;
}
auto const& key_path_string = key_path.get<String>();
// 2. If keyPath is the empty string, return value and skip the remaining steps.
if (key_path_string.is_empty())
return value;
// 3. Let identifiers be the result of strictly splitting keyPath on U+002E FULL STOP characters (.).
// 4. For each identifier of identifiers, jump to the appropriate step below:
TRY(key_path_string.bytes_as_string_view().for_each_split_view('.', SplitBehavior::KeepEmpty | SplitBehavior::KeepTrailingSeparator, [&](auto const& identifier) -> ErrorOr<void> {
// If Type(value) is String, and identifier is "length"
if (value.is_string() && identifier == "length") {
// Let value be a Number equal to the number of elements in value.
value = JS::Value(value.as_string().utf16_string_view().length_in_code_units());
}
// If value is an Array and identifier is "length"
else if (value.is_object() && is<JS::Array>(value.as_object()) && identifier == "length") {
// Let value be ! ToLength(! Get(value, "length")).
value = JS::Value(MUST(length_of_array_like(realm.vm(), value.as_object())));
}
// If value is a Blob and identifier is "size"
else if (value.is_object() && is<FileAPI::Blob>(value.as_object()) && identifier == "size") {
// Let value be values size.
value = JS::Value(static_cast<FileAPI::Blob&>(value.as_object()).size());
}
// If value is a Blob and identifier is "type"
else if (value.is_object() && is<FileAPI::Blob>(value.as_object()) && identifier == "type") {
// Let value be a String equal to values type.
value = JS::PrimitiveString::create(realm.vm(), static_cast<FileAPI::Blob&>(value.as_object()).type());
}
// If value is a File and identifier is "name"
else if (value.is_object() && is<FileAPI::File>(value.as_object()) && identifier == "name") {
// Let value be a String equal to values name.
value = JS::PrimitiveString::create(realm.vm(), static_cast<FileAPI::File&>(value.as_object()).name());
}
// If value is a File and identifier is "lastModified"
else if (value.is_object() && is<FileAPI::File>(value.as_object()) && identifier == "lastModified") {
// Let value be a Number equal to values lastModified.
value = JS::Value(static_cast<double>(static_cast<FileAPI::File&>(value.as_object()).last_modified()));
}
// Otherwise
else {
// 1. If Type(value) is not Object, return failure.
if (!value.is_object())
return Error::from_string_literal("Value is not an object");
auto identifier_property = String::from_utf8_without_validation(identifier.bytes());
// 2. Let hop be ! HasOwnProperty(value, identifier).
auto hop = MUST(value.as_object().has_own_property(identifier_property));
// 3. If hop is false, return failure.
if (!hop)
return Error::from_string_literal("Property does not exist");
// 4. Let value be ! Get(value, identifier).
value = MUST(value.as_object().get(identifier_property));
// 5. If value is undefined, return failure.
if (value.is_undefined())
return Error::from_string_literal("Value is undefined");
}
return {};
}));
// 5. Assert: value is not an abrupt completion.
// NOTE: Step 4 above makes this assertion via MUST
// 6. Return value.
return value;
}
}

View file

@ -30,5 +30,6 @@ GC::Ref<HTML::DOMStringList> create_a_sorted_name_list(JS::Realm&, Vector<String
void commit_a_transaction(JS::Realm&, GC::Ref<IDBTransaction>);
WebIDL::ExceptionOr<JS::Value> clone_in_realm(JS::Realm&, JS::Value, GC::Ref<IDBTransaction>);
WebIDL::ExceptionOr<ErrorOr<GC::Ref<Key>>> convert_a_value_to_a_multi_entry_key(JS::Realm&, JS::Value);
WebIDL::ExceptionOr<ErrorOr<JS::Value>> evaluate_key_path_on_a_value(JS::Realm&, JS::Value, KeyPath const&);
}