diff --git a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp index c1533bc7ff7..b96a5031872 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp +++ b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include #include #include @@ -855,4 +857,123 @@ WebIDL::ExceptionOr>> 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> 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>()) { + auto const& key_path_list = key_path.get>(); + + // 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(); + + // 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 { + // 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(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(value.as_object()) && identifier == "size") { + // Let value be value’s size. + value = JS::Value(static_cast(value.as_object()).size()); + } + + // If value is a Blob and identifier is "type" + else if (value.is_object() && is(value.as_object()) && identifier == "type") { + // Let value be a String equal to value’s type. + value = JS::PrimitiveString::create(realm.vm(), static_cast(value.as_object()).type()); + } + + // If value is a File and identifier is "name" + else if (value.is_object() && is(value.as_object()) && identifier == "name") { + // Let value be a String equal to value’s name. + value = JS::PrimitiveString::create(realm.vm(), static_cast(value.as_object()).name()); + } + + // If value is a File and identifier is "lastModified" + else if (value.is_object() && is(value.as_object()) && identifier == "lastModified") { + // Let value be a Number equal to value’s lastModified. + value = JS::Value(static_cast(static_cast(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; +} + } diff --git a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h index 9b0c505cc03..2eed3c31a3a 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h +++ b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.h @@ -30,5 +30,6 @@ GC::Ref create_a_sorted_name_list(JS::Realm&, Vector); WebIDL::ExceptionOr clone_in_realm(JS::Realm&, JS::Value, GC::Ref); WebIDL::ExceptionOr>> convert_a_value_to_a_multi_entry_key(JS::Realm&, JS::Value); +WebIDL::ExceptionOr> evaluate_key_path_on_a_value(JS::Realm&, JS::Value, KeyPath const&); }