LibWeb: Support [de]serialization for {Map, Set, Object, Array} objects

This commit is contained in:
Idan Horowitz 2023-11-11 20:14:46 +02:00 committed by Andreas Kling
parent 4e89ec7fd5
commit 20734ac335
Notes: sideshowbarker 2024-07-16 22:17:03 +09:00
8 changed files with 447 additions and 190 deletions

View file

@ -8,5 +8,10 @@ This is a String object
/abc/gimsuy
Error
URIError: hello
{"1":2,"a":"b"}
1,4,aaaa
true
1
true
[object ArrayBuffer]
ERROR: DataCloneError: Cannot serialize Symbol

View file

@ -8,7 +8,7 @@ originParsedBeforeSerializeError.message: Invalid URL for targetOrigin: 'aaaa'
originParsedBeforeSerializeError.constructor === window.DOMException: true
serializeError instanceof DOMException: true
serializeError.name: DataCloneError
serializeError.message: Unsupported type
serializeError.message: Cannot serialize platform objects
serializeError.constructor === window.DOMException: true
originIframeError instanceof DOMException: false
originIframeError instanceof iframe.contentWindow.DOMException: true
@ -25,7 +25,7 @@ originParsedBeforeSerializeIframeError.constructor === iframe.contentWindow.DOME
serializeIframeError instanceof DOMException: false
serializeIframeError instanceof iframe.contentWindow.DOMException: true
serializeIframeError.name: DataCloneError
serializeIframeError.message: Unsupported type
serializeIframeError.message: Cannot serialize platform objects
serializeIframeError.constructor === DOMException: false
serializeIframeError.constructor === iframe.contentWindow.DOMException: true
Message 1 data: undefined

View file

@ -11,6 +11,15 @@
println(structuredClone(/abc/gimsuy));
println(structuredClone(new Error()));
println(structuredClone(new URIError("hello")));
println(structuredClone(JSON.stringify({"a": "b", 1: 2})));
println(structuredClone([1, 4, "aaaa"]));
println(structuredClone(new Set(["a", "b", "c"])).has("b"));
println(structuredClone(new Map([["a", 0], ["b", 1], ["c", 2]])).get("b"));
const obj = {"a": 1, "c": 3};
obj["b"] = obj;
const result = structuredClone(obj);
println(result === result["b"]);
{
let arrayBuffer = new ArrayBuffer(6);

View file

@ -2,6 +2,7 @@
* Copyright (c) 2022, Daniel Ehrenberg <dan@littledan.dev>
* Copyright (c) 2022, Andrew Kaster <akaster@serenityos.org>
* Copyright (c) 2023, Kenneth Myhra <kennethmyhra@serenityos.org>
* Copyright (c) 2023, Idan Horowitz <idan.horowitz@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -10,15 +11,18 @@
#include <AK/String.h>
#include <AK/Vector.h>
#include <LibJS/Forward.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/ArrayBuffer.h>
#include <LibJS/Runtime/BigInt.h>
#include <LibJS/Runtime/BigIntObject.h>
#include <LibJS/Runtime/BooleanObject.h>
#include <LibJS/Runtime/DataView.h>
#include <LibJS/Runtime/Date.h>
#include <LibJS/Runtime/Map.h>
#include <LibJS/Runtime/NumberObject.h>
#include <LibJS/Runtime/PrimitiveString.h>
#include <LibJS/Runtime/RegExpObject.h>
#include <LibJS/Runtime/Set.h>
#include <LibJS/Runtime/StringObject.h>
#include <LibJS/Runtime/TypedArray.h>
#include <LibJS/Runtime/VM.h>
@ -28,8 +32,6 @@
namespace Web::HTML {
static WebIDL::ExceptionOr<JS::Value> structured_deserialize_impl(JS::VM& vm, ReadonlySpan<u32> serialized, JS::Realm& target_realm, SerializationMemory& memory);
// Binary format:
// A list of adjacent shallow values, which may contain references to other
// values (noted by their position in the list, one value following another).
@ -85,8 +87,18 @@ enum ValueTag {
ArrayBufferView,
MapObject,
SetObject,
ErrorObject,
ArrayObject,
Object,
ObjectReference,
// TODO: Define many more types
// This tag or higher are understood to be errors
@ -128,14 +140,13 @@ public:
WebIDL::ExceptionOr<SerializationRecord> serialize(JS::Value value)
{
// 2. If memory[value] exists, then return memory[value].
// FIXME: Do callers actually need a copy? or can they get away with a range?
if (m_memory.contains(value)) {
auto range = m_memory.get(value).value();
return m_serialized.span().slice(range.start, range.end);
auto index = m_memory.get(value).value();
return Vector<u32> { ValueTag::ObjectReference, index };
}
// 3. Let deep be false.
[[maybe_unused]] bool deep = false;
auto deep = false;
bool return_primitive_type = true;
// 4. If Type(value) is Undefined, Null, Boolean, Number, BigInt, or String, then return { [[Type]]: "primitive", [[Value]]: value }.
@ -169,9 +180,6 @@ public:
return WebIDL::DataCloneError::create(*m_vm.current_realm(), "Cannot serialize Symbol"_fly_string);
// 6. Let serialized be an uninitialized value.
// NOTE: We use the range of the soon-to-be-serialized value in our serialized data buffer
// to be the `serialized` spec value.
auto serialized_start = m_serialized.size();
// 7. If value has a [[BooleanData]] internal slot, then set serialized to { [[Type]]: "Boolean", [[BooleanData]]: value.[[BooleanData]] }.
if (value.is_object() && is<JS::BooleanObject>(value.as_object())) {
@ -235,6 +243,22 @@ public:
TRY(serialize_viewed_array_buffer(m_serialized, static_cast<JS::DataView&>(value.as_object())));
}
// 15. Otherwise, if value has [[MapData]] internal slot, then:
else if (value.is_object() && is<JS::Map>(value.as_object())) {
// 1. Set serialized to { [[Type]]: "Map", [[MapData]]: a new empty List }.
m_serialized.append(ValueTag::MapObject);
// 2. Set deep to true.
deep = true;
}
// 16. Otherwise, if value has [[SetData]] internal slot, then:
else if (value.is_object() && is<JS::Set>(value.as_object())) {
// 1. Set serialized to { [[Type]]: "Set", [[SetData]]: a new empty List }.
m_serialized.append(ValueTag::SetObject);
// 2. Set deep to true.
deep = true;
}
// 17. Otherwise, if value has an [[ErrorData]] internal slot and value is not a platform object, then:
else if (value.is_object() && is<JS::Error>(value.as_object()) && !is<Bindings::PlatformObject>(value.as_object())) {
// 1. Let name be ? Get(value, "name").
@ -264,6 +288,23 @@ public:
TRY(serialize_string(m_serialized, *message));
}
// 18. Otherwise, if value is an Array exotic object, then:
else if (value.is_object() && is<JS::Array>(value.as_object())) {
// 1. Let valueLenDescriptor be ? OrdinaryGetOwnProperty(value, "length").
// 2. Let valueLen be valueLenDescriptor.[[Value]].
// NON-STANDARD: Array objects in LibJS do not have a real length property, so it must be accessed the usual way
u64 length = MUST(JS::length_of_array_like(m_vm, value.as_object()));
// 3. Set serialized to { [[Type]]: "Array", [[Length]]: valueLen, [[Properties]]: a new empty List }.
m_serialized.append(ValueTag::ArrayObject);
m_serialized.append(bit_cast<u32*>(&length), 2);
// 4. Set deep to true.
deep = true;
}
// FIXME: 19. Otherwise, if value is a platform object that is a serializable object:
// 20. Otherwise, if value is a platform object, then throw a "DataCloneError" DOMException.
else if (value.is_object() && is<Bindings::PlatformObject>(value.as_object())) {
return throw_completion(WebIDL::DataCloneError::create(*m_vm.current_realm(), "Cannot serialize platform objects"_fly_string));
@ -278,23 +319,106 @@ public:
// FIXME: 23. Otherwise, if value is an exotic object and value is not the %Object.prototype% intrinsic object associated with any realm, then throw a "DataCloneError" DOMException.
// 15, 16, 18, 19, 24: FIXME: Serialize other data types
// 24. Otherwise:
else {
return throw_completion(WebIDL::DataCloneError::create(*m_vm.current_realm(), "Unsupported type"_fly_string));
// 1. Set serialized to { [[Type]]: "Object", [[Properties]]: a new empty List }.
m_serialized.append(ValueTag::Object);
// 2. Set deep to true.
deep = true;
}
// 25. Set memory[value] to serialized.
auto serialized_end = m_serialized.size();
m_memory.set(make_handle(value), { serialized_start, serialized_end });
m_memory.set(make_handle(value), m_next_id++);
// Second pass: Update the objects to point to other objects in memory
// 26. If deep is true, then:
if (deep) {
// 1. If value has a [[MapData]] internal slot, then:
if (value.is_object() && is<JS::Map>(value.as_object())) {
auto const& map = static_cast<JS::Map const&>(value.as_object());
// 1. Let copiedList be a new empty List.
Vector<JS::Value> copied_list;
copied_list.ensure_capacity(map.map_size() * 2);
// 2. For each Record { [[Key]], [[Value]] } entry of value.[[MapData]]:
for (auto const& entry : static_cast<JS::Map const&>(value.as_object())) {
// 1. Let copiedEntry be a new Record { [[Key]]: entry.[[Key]], [[Value]]: entry.[[Value]] }.
// 2. If copiedEntry.[[Key]] is not the special value empty, append copiedEntry to copiedList.
copied_list.append(entry.key);
copied_list.append(entry.value);
}
u64 size = map.map_size();
m_serialized.append(bit_cast<u32*>(&size), 2);
// 3. For each Record { [[Key]], [[Value]] } entry of copiedList:
for (auto copied_value : copied_list) {
// 1. Let serializedKey be ? StructuredSerializeInternal(entry.[[Key]], forStorage, memory).
// 2. Let serializedValue be ? StructuredSerializeInternal(entry.[[Value]], forStorage, memory).
auto serialized_value = TRY(structured_serialize_internal(m_vm, copied_value, m_for_storage, m_memory));
// 3. Append { [[Key]]: serializedKey, [[Value]]: serializedValue } to serialized.[[MapData]].
m_serialized.extend(serialized_value);
}
}
// 2. Otherwise, if value has a [[SetData]] internal slot, then:
else if (value.is_object() && is<JS::Set>(value.as_object())) {
auto const& set = static_cast<JS::Set const&>(value.as_object());
// 1. Let copiedList be a new empty List.
Vector<JS::Value> copied_list;
copied_list.ensure_capacity(set.set_size());
// 2. For each entry of value.[[SetData]]:
for (auto const& entry : static_cast<JS::Set const&>(value.as_object())) {
// 1. If entry is not the special value empty, append entry to copiedList.
copied_list.append(entry.key);
}
u64 size = set.set_size();
m_serialized.append(bit_cast<u32*>(&size), 2);
// 3. For each entry of copiedList:
for (auto copied_value : copied_list) {
// 1. Let serializedEntry be ? StructuredSerializeInternal(entry, forStorage, memory).
auto serialized_value = TRY(structured_serialize_internal(m_vm, copied_value, m_for_storage, m_memory));
// 2. Append serializedEntry to serialized.[[SetData]].
m_serialized.extend(serialized_value);
}
}
// FIXME: 3. Otherwise, if value is a platform object that is a serializable object, then perform the serialization steps for value's primary interface, given value, serialized, and forStorage.
// 4. Otherwise, for each key in ! EnumerableOwnProperties(value, key):
else {
u64 property_count = 0;
auto count_offset = m_serialized.size();
m_serialized.append(bit_cast<u32*>(&property_count), 2);
for (auto key : MUST(value.as_object().enumerable_own_property_names(JS::Object::PropertyKind::Key))) {
auto property_key = MUST(JS::PropertyKey::from_value(m_vm, key));
// 1. If ! HasOwnProperty(value, key) is true, then:
if (MUST(value.as_object().has_own_property(property_key))) {
// 1. Let inputValue be ? value.[[Get]](key, value).
auto input_value = TRY(value.as_object().internal_get(property_key, value));
// 2. Let outputValue be ? StructuredSerializeInternal(inputValue, forStorage, memory).
auto output_value = TRY(structured_serialize_internal(m_vm, input_value, m_for_storage, m_memory));
// 3. Append { [[Key]]: key, [[Value]]: outputValue } to serialized.[[Properties]].
TRY(serialize_string(m_serialized, key.as_string()));
m_serialized.extend(output_value);
property_count++;
}
}
memcpy(m_serialized.data() + count_offset, &property_count, sizeof(property_count));
}
}
// 27. Return serialized.
return m_serialized;
}
private:
JS::VM& m_vm;
SerializationMemory& m_memory; // JS value -> index
u32 m_next_id { 0 };
SerializationRecord m_serialized;
bool m_for_storage { false };
@ -408,8 +532,6 @@ private:
// [[ArrayBufferSerialized]]: bufferSerialized, [[ByteLength]]: value.[[ByteLength]], [[ByteOffset]]: value.[[ByteOffset]] }.
if constexpr (IsSame<ViewType, JS::DataView>) {
vector.append(ValueTag::ArrayBufferView);
u64 const serialized_buffer_size = buffer_serialized.size();
vector.append(bit_cast<u32*>(&serialized_buffer_size), 2);
vector.extend(move(buffer_serialized)); // [[ArrayBufferSerialized]]
TRY(serialize_string(vector, "DataView"_string)); // [[Constructor]]
vector.append(view.byte_length());
@ -424,8 +546,6 @@ private:
// [[ArrayBufferSerialized]]: bufferSerialized, [[ByteLength]]: value.[[ByteLength]],
// [[ByteOffset]]: value.[[ByteOffset]], [[ArrayLength]]: value.[[ArrayLength]] }.
vector.append(ValueTag::ArrayBufferView);
u64 const serialized_buffer_size = buffer_serialized.size();
vector.append(bit_cast<u32*>(&serialized_buffer_size), 2);
vector.extend(move(buffer_serialized)); // [[ArrayBufferSerialized]]
TRY(serialize_string(vector, view.element_name())); // [[Constructor]]
vector.append(view.byte_length());
@ -438,187 +558,320 @@ private:
class Deserializer {
public:
Deserializer(JS::VM& vm, JS::Realm& target_realm, ReadonlySpan<u32> v, SerializationMemory& serialization_memory)
Deserializer(JS::VM& vm, JS::Realm& target_realm, ReadonlySpan<u32> serialized, DeserializationMemory& memory)
: m_vm(vm)
, m_vector(v)
, m_memory(target_realm.heap())
, m_serialization_memory(serialization_memory)
, m_serialized(serialized)
, m_memory(memory)
{
VERIFY(vm.current_realm() == &target_realm);
}
WebIDL::ExceptionOr<void> deserialize()
// https://html.spec.whatwg.org/multipage/structured-data.html#structureddeserialize
WebIDL::ExceptionOr<JS::Value> deserialize()
{
// First pass: fill up the memory with new values
u32 position = 0;
while (position < m_vector.size()) {
switch (m_vector[position++]) {
case ValueTag::UndefinedPrimitive: {
m_memory.append(JS::js_undefined());
break;
}
case ValueTag::NullPrimitive: {
m_memory.append(JS::js_null());
break;
}
case ValueTag::BooleanPrimitive: {
m_memory.append(JS::Value(static_cast<bool>(m_vector[position++])));
break;
}
case ValueTag::NumberPrimitive: {
u32 bits[2];
bits[0] = m_vector[position++];
bits[1] = m_vector[position++];
double value = *bit_cast<double*>(&bits);
m_memory.append(JS::Value(value));
break;
}
case ValueTag::BigIntPrimitive: {
auto big_int = TRY(deserialize_big_int_primitive(m_vm, m_vector, position));
m_memory.append(JS::Value { big_int });
break;
}
case ValueTag::StringPrimitive: {
auto string = TRY(deserialize_string_primitive(m_vm, m_vector, position));
m_memory.append(JS::Value { string });
break;
}
case BooleanObject: {
auto* realm = m_vm.current_realm();
bool const value = static_cast<bool>(m_vector[position++]);
m_memory.append(JS::BooleanObject::create(*realm, value));
break;
}
case ValueTag::NumberObject: {
auto* realm = m_vm.current_realm();
u32 bits[2];
bits[0] = m_vector[position++];
bits[1] = m_vector[position++];
double const value = *bit_cast<double*>(&bits);
m_memory.append(JS::NumberObject::create(*realm, value));
break;
}
case ValueTag::BigIntObject: {
auto* realm = m_vm.current_realm();
auto big_int = TRY(deserialize_big_int_primitive(m_vm, m_vector, position));
m_memory.append(JS::BigIntObject::create(*realm, big_int));
break;
}
case ValueTag::StringObject: {
auto* realm = m_vm.current_realm();
auto string = TRY(deserialize_string_primitive(m_vm, m_vector, position));
m_memory.append(JS::StringObject::create(*realm, string, realm->intrinsics().string_prototype()));
break;
}
case ValueTag::DateObject: {
auto* realm = m_vm.current_realm();
u32 bits[2];
bits[0] = m_vector[position++];
bits[1] = m_vector[position++];
double const value = *bit_cast<double*>(&bits);
m_memory.append(JS::Date::create(*realm, value));
break;
}
case ValueTag::RegExpObject: {
auto pattern = TRY(deserialize_string_primitive(m_vm, m_vector, position));
auto flags = TRY(deserialize_string_primitive(m_vm, m_vector, position));
m_memory.append(TRY(JS::regexp_create(m_vm, move(pattern), move(flags))));
break;
}
case ValueTag::ArrayBuffer: {
auto* realm = m_vm.current_realm();
auto bytes = TRY(deserialize_bytes(m_vm, m_vector, position));
m_memory.append(JS::ArrayBuffer::create(*realm, move(bytes)));
break;
}
case ValueTag::ArrayBufferView: {
auto* realm = m_vm.current_realm();
u32 size_bits[2];
size_bits[0] = m_vector[position++];
size_bits[1] = m_vector[position++];
u64 const array_buffer_size = *bit_cast<u64*>(&size_bits);
auto array_buffer_value = TRY(structured_deserialize_impl(m_vm, m_vector.slice(position, array_buffer_size), *realm, m_serialization_memory));
auto& array_buffer = verify_cast<JS::ArrayBuffer>(array_buffer_value.as_object());
position += array_buffer_size;
auto constructor_name = TRY(deserialize_string(m_vm, m_vector, position));
u32 byte_length = m_vector[position++];
u32 byte_offset = m_vector[position++];
auto tag = m_serialized[m_position++];
if (constructor_name == "DataView"sv) {
m_memory.append(JS::DataView::create(*realm, &array_buffer, byte_length, byte_offset));
} else {
u32 array_length = m_vector[position++];
JS::GCPtr<JS::TypedArrayBase> typed_array_ptr;
// 2. If memory[serialized] exists, then return memory[serialized].
if (tag == ValueTag::ObjectReference) {
auto index = m_serialized[m_position++];
return m_memory[index];
}
// 3. Let deep be false.
auto deep = false;
// 4. Let value be an uninitialized value.
JS::Value value;
auto is_primitive = false;
switch (tag) {
// 5. If serialized.[[Type]] is "primitive", then set value to serialized.[[Value]].
case ValueTag::UndefinedPrimitive: {
value = JS::js_undefined();
is_primitive = true;
break;
}
case ValueTag::NullPrimitive: {
value = JS::js_null();
is_primitive = true;
break;
}
case ValueTag::BooleanPrimitive: {
value = JS::Value(static_cast<bool>(m_serialized[m_position++]));
is_primitive = true;
break;
}
case ValueTag::NumberPrimitive: {
u32 bits[2] = {};
bits[0] = m_serialized[m_position++];
bits[1] = m_serialized[m_position++];
auto double_value = *bit_cast<double*>(&bits);
value = JS::Value(double_value);
is_primitive = true;
break;
}
case ValueTag::BigIntPrimitive: {
auto big_int = TRY(deserialize_big_int_primitive(m_vm, m_serialized, m_position));
value = JS::Value { big_int };
is_primitive = true;
break;
}
case ValueTag::StringPrimitive: {
auto string = TRY(deserialize_string_primitive(m_vm, m_serialized, m_position));
value = JS::Value { string };
is_primitive = true;
break;
}
// 6. Otherwise, if serialized.[[Type]] is "Boolean", then set value to a new Boolean object in targetRealm whose [[BooleanData]] internal slot value is serialized.[[BooleanData]].
case BooleanObject: {
auto* realm = m_vm.current_realm();
auto bool_value = static_cast<bool>(m_serialized[m_position++]);
value = JS::BooleanObject::create(*realm, bool_value);
break;
}
// 7. Otherwise, if serialized.[[Type]] is "Number", then set value to a new Number object in targetRealm whose [[NumberData]] internal slot value is serialized.[[NumberData]].
case ValueTag::NumberObject: {
auto* realm = m_vm.current_realm();
u32 bits[2];
bits[0] = m_serialized[m_position++];
bits[1] = m_serialized[m_position++];
auto double_value = *bit_cast<double*>(&bits);
value = JS::NumberObject::create(*realm, double_value);
break;
}
// 8. Otherwise, if serialized.[[Type]] is "BigInt", then set value to a new BigInt object in targetRealm whose [[BigIntData]] internal slot value is serialized.[[BigIntData]].
case ValueTag::BigIntObject: {
auto* realm = m_vm.current_realm();
auto big_int = TRY(deserialize_big_int_primitive(m_vm, m_serialized, m_position));
value = JS::BigIntObject::create(*realm, big_int);
break;
}
// 9. Otherwise, if serialized.[[Type]] is "String", then set value to a new String object in targetRealm whose [[StringData]] internal slot value is serialized.[[StringData]].
case ValueTag::StringObject: {
auto* realm = m_vm.current_realm();
auto string = TRY(deserialize_string_primitive(m_vm, m_serialized, m_position));
value = JS::StringObject::create(*realm, string, realm->intrinsics().string_prototype());
break;
}
// 10. Otherwise, if serialized.[[Type]] is "Date", then set value to a new Date object in targetRealm whose [[DateValue]] internal slot value is serialized.[[DateValue]].
case ValueTag::DateObject: {
auto* realm = m_vm.current_realm();
u32 bits[2];
bits[0] = m_serialized[m_position++];
bits[1] = m_serialized[m_position++];
auto double_value = *bit_cast<double*>(&bits);
value = JS::Date::create(*realm, double_value);
break;
}
// 11. Otherwise, if serialized.[[Type]] is "RegExp", then set value to a new RegExp object in targetRealm whose [[RegExpMatcher]] internal slot value is serialized.[[RegExpMatcher]],
// whose [[OriginalSource]] internal slot value is serialized.[[OriginalSource]], and whose [[OriginalFlags]] internal slot value is serialized.[[OriginalFlags]].
case ValueTag::RegExpObject: {
auto pattern = TRY(deserialize_string_primitive(m_vm, m_serialized, m_position));
auto flags = TRY(deserialize_string_primitive(m_vm, m_serialized, m_position));
value = TRY(JS::regexp_create(m_vm, move(pattern), move(flags)));
break;
}
// FIXME: 12. Otherwise, if serialized.[[Type]] is "SharedArrayBuffer", then:
// FIXME: 13. Otherwise, if serialized.[[Type]] is "GrowableSharedArrayBuffer", then:
// 14. Otherwise, if serialized.[[Type]] is "ArrayBuffer", then set value to a new ArrayBuffer object in targetRealm whose [[ArrayBufferData]] internal slot value is serialized.[[ArrayBufferData]], and whose [[ArrayBufferByteLength]] internal slot value is serialized.[[ArrayBufferByteLength]].
case ValueTag::ArrayBuffer: {
auto* realm = m_vm.current_realm();
// If this throws an exception, catch it, and then throw a "DataCloneError" DOMException.
auto bytes_or_error = deserialize_bytes(m_vm, m_serialized, m_position);
if (bytes_or_error.is_error())
return WebIDL::DataCloneError::create(*m_vm.current_realm(), "out of memory"_fly_string);
value = JS::ArrayBuffer::create(*realm, bytes_or_error.release_value());
break;
}
// FIXME: 15. Otherwise, if serialized.[[Type]] is "ResizableArrayBuffer", then set value to a new ArrayBuffer object in targetRealm whose [[ArrayBufferData]] internal slot value is serialized.[[ArrayBufferData]], whose [[ArrayBufferByteLength]] internal slot value is serialized.[[ArrayBufferByteLength]], and whose [[ArrayBufferMaxByteLength]] internal slot value is a serialized.[[ArrayBufferMaxByteLength]].
// 16. Otherwise, if serialized.[[Type]] is "ArrayBufferView", then:
case ValueTag::ArrayBufferView: {
auto* realm = m_vm.current_realm();
auto array_buffer_value = TRY(deserialize());
auto& array_buffer = verify_cast<JS::ArrayBuffer>(array_buffer_value.as_object());
auto constructor_name = TRY(deserialize_string(m_vm, m_serialized, m_position));
u32 byte_length = m_serialized[m_position++];
u32 byte_offset = m_serialized[m_position++];
if (constructor_name == "DataView"sv) {
value = JS::DataView::create(*realm, &array_buffer, byte_length, byte_offset);
} else {
u32 array_length = m_serialized[m_position++];
JS::GCPtr<JS::TypedArrayBase> typed_array_ptr;
#define CREATE_TYPED_ARRAY(ClassName) \
if (constructor_name == #ClassName##sv) \
typed_array_ptr = JS::ClassName::create(*realm, array_length, array_buffer);
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, Type) \
CREATE_TYPED_ARRAY(ClassName)
JS_ENUMERATE_TYPED_ARRAYS
JS_ENUMERATE_TYPED_ARRAYS
#undef __JS_ENUMERATE
#undef CREATE_TYPED_ARRAY
VERIFY(typed_array_ptr != nullptr); // FIXME: Handle errors better here? Can a fuzzer put weird stuff in the buffer?
typed_array_ptr->set_byte_length(byte_length);
typed_array_ptr->set_byte_offset(byte_offset);
m_memory.append(typed_array_ptr);
}
break;
VERIFY(typed_array_ptr != nullptr); // FIXME: Handle errors better here? Can a fuzzer put weird stuff in the buffer?
typed_array_ptr->set_byte_length(byte_length);
typed_array_ptr->set_byte_offset(byte_offset);
value = typed_array_ptr;
}
case ValueTag::ErrorObject: {
auto& realm = *m_vm.current_realm();
auto type = static_cast<ErrorType>(m_vector[position++]);
auto has_message = static_cast<bool>(m_vector[position++]);
if (has_message) {
auto message = TRY(deserialize_string(m_vm, m_vector, position));
switch (type) {
case ErrorType::Error:
m_memory.append(JS::Error::create(realm, message));
break;
break;
}
// 17. Otherwise, if serialized.[[Type]] is "Map", then:
case ValueTag::MapObject: {
auto& realm = *m_vm.current_realm();
// 1. Set value to a new Map object in targetRealm whose [[MapData]] internal slot value is a new empty List.
value = JS::Map::create(realm);
// 2. Set deep to true.
deep = true;
break;
}
// 18. Otherwise, if serialized.[[Type]] is "Set", then:
case ValueTag::SetObject: {
auto& realm = *m_vm.current_realm();
// 1. Set value to a new Set object in targetRealm whose [[SetData]] internal slot value is a new empty List.
value = JS::Set::create(realm);
// 2. Set deep to true.
deep = true;
break;
}
// 19. Otherwise, if serialized.[[Type]] is "Array", then:
case ValueTag::ArrayObject: {
auto& realm = *m_vm.current_realm();
// 1. Let outputProto be targetRealm.[[Intrinsics]].[[%Array.prototype%]].
// 2. Set value to ! ArrayCreate(serialized.[[Length]], outputProto).
auto length = read_u64();
value = MUST(JS::Array::create(realm, length));
// 3. Set deep to true.
deep = true;
break;
}
// 20. Otherwise, if serialized.[[Type]] is "Object", then:
case ValueTag::Object: {
auto& realm = *m_vm.current_realm();
// 1. Set value to a new Object in targetRealm.
value = JS::Object::create(realm, realm.intrinsics().object_prototype());
// 2. Set deep to true.
deep = true;
break;
}
// 21. Otherwise, if serialized.[[Type]] is "Error", then:
case ValueTag::ErrorObject: {
auto& realm = *m_vm.current_realm();
auto type = static_cast<ErrorType>(m_serialized[m_position++]);
auto has_message = static_cast<bool>(m_serialized[m_position++]);
if (has_message) {
auto message = TRY(deserialize_string(m_vm, m_serialized, m_position));
switch (type) {
case ErrorType::Error:
value = JS::Error::create(realm, message);
break;
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
case ErrorType::ClassName: \
m_memory.append(JS::ClassName::create(realm, message)); \
value = JS::ClassName::create(realm, message); \
break;
JS_ENUMERATE_NATIVE_ERRORS
JS_ENUMERATE_NATIVE_ERRORS
#undef __JS_ENUMERATE
}
} else {
switch (type) {
case ErrorType::Error:
m_memory.append(JS::Error::create(realm));
break;
}
} else {
switch (type) {
case ErrorType::Error:
value = JS::Error::create(realm);
break;
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
case ErrorType::ClassName: \
m_memory.append(JS::ClassName::create(realm)); \
value = JS::ClassName::create(realm); \
break;
JS_ENUMERATE_NATIVE_ERRORS
JS_ENUMERATE_NATIVE_ERRORS
#undef __JS_ENUMERATE
}
}
break;
}
default:
m_error = "Unsupported type"_fly_string;
break;
break;
}
// 22. Otherwise:
default:
return WebIDL::DataCloneError::create(*m_vm.current_realm(), "Unsupported type"_fly_string);
}
// 23. Set memory[serialized] to value.
// IMPLEMENTATION DEFINED: We don't add primitive values to the memory to match the serialization indices (which also doesn't add them)
if (!is_primitive)
m_memory.append(value);
// 24. If deep is true, then:
if (deep) {
// 1. If serialized.[[Type]] is "Map", then:
if (tag == ValueTag::MapObject) {
auto& map = static_cast<JS::Map&>(value.as_object());
auto length = read_u64();
// 1. For each Record { [[Key]], [[Value]] } entry of serialized.[[MapData]]:
for (u64 i = 0u; i < length; ++i) {
// 1. Let deserializedKey be ? StructuredDeserialize(entry.[[Key]], targetRealm, memory).
auto deserialized_key = TRY(deserialize());
// 2. Let deserializedValue be ? StructuredDeserialize(entry.[[Value]], targetRealm, memory).
auto deserialized_value = TRY(deserialize());
// 3. Append { [[Key]]: deserializedKey, [[Value]]: deserializedValue } to value.[[MapData]].
map.map_set(deserialized_key, deserialized_value);
}
}
// 2. Otherwise, if serialized.[[Type]] is "Set", then:
else if (tag == ValueTag::SetObject) {
auto& set = static_cast<JS::Set&>(value.as_object());
auto length = read_u64();
// 1. For each entry of serialized.[[SetData]]:
for (u64 i = 0u; i < length; ++i) {
// 1. Let deserializedEntry be ? StructuredDeserialize(entry, targetRealm, memory).
auto deserialized_entry = TRY(deserialize());
// 2. Append deserializedEntry to value.[[SetData]].
set.set_add(deserialized_entry);
}
}
// 3. Otherwise, if serialized.[[Type]] is "Array" or "Object", then:
else if (tag == ValueTag::ArrayObject || tag == ValueTag::Object) {
auto& object = value.as_object();
auto length = read_u64();
// 1. For each Record { [[Key]], [[Value]] } entry of serialized.[[Properties]]:
for (u64 i = 0u; i < length; ++i) {
auto key = TRY(deserialize_string(m_vm, m_serialized, m_position));
// 1. Let deserializedValue be ? StructuredDeserialize(entry.[[Value]], targetRealm, memory).
auto deserialized_value = TRY(deserialize());
// 2. Let result be ! CreateDataProperty(value, entry.[[Key]], deserializedValue).
auto result = MUST(object.create_data_property(key.to_deprecated_string(), deserialized_value));
// 3. Assert: result is true.
VERIFY(result);
}
}
// 4. Otherwise:
else {
// FIXME: 1. Perform the appropriate deserialization steps for the interface identified by serialized.[[Type]], given serialized, value, and targetRealm.
VERIFY_NOT_REACHED();
}
}
return {};
}
WebIDL::ExceptionOr<JS::Value> result()
{
if (!m_error.has_value())
return m_memory[0];
return WebIDL::DataCloneError::create(*m_vm.current_realm(), m_error.value());
// 25. Return value.
return value;
}
private:
JS::VM& m_vm;
ReadonlySpan<u32> m_vector;
ReadonlySpan<u32> m_serialized;
size_t m_position { 0 };
JS::MarkedVector<JS::Value> m_memory; // Index -> JS value
Optional<FlyString> m_error;
SerializationMemory& m_serialization_memory;
static WebIDL::ExceptionOr<ByteBuffer> deserialize_bytes(JS::VM& vm, ReadonlySpan<u32> vector, u32& position)
u64 read_u64()
{
u64 value;
memcpy(&value, m_serialized.offset_pointer(m_position), sizeof(value));
m_position += 2;
return value;
}
static WebIDL::ExceptionOr<ByteBuffer> deserialize_bytes(JS::VM& vm, ReadonlySpan<u32> vector, size_t& position)
{
u32 size_bits[2];
size_bits[0] = vector[position++];
@ -638,13 +891,13 @@ private:
return bytes;
}
static WebIDL::ExceptionOr<String> deserialize_string(JS::VM& vm, ReadonlySpan<u32> vector, u32& position)
static WebIDL::ExceptionOr<String> deserialize_string(JS::VM& vm, ReadonlySpan<u32> vector, size_t& position)
{
auto bytes = TRY(deserialize_bytes(vm, vector, position));
return TRY_OR_THROW_OOM(vm, String::from_utf8(StringView { bytes }));
}
static WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::PrimitiveString>> deserialize_string_primitive(JS::VM& vm, ReadonlySpan<u32> vector, u32& position)
static WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::PrimitiveString>> deserialize_string_primitive(JS::VM& vm, ReadonlySpan<u32> vector, size_t& position)
{
auto bytes = TRY(deserialize_bytes(vm, vector, position));
@ -653,7 +906,7 @@ private:
}));
}
static WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::BigInt>> deserialize_big_int_primitive(JS::VM& vm, ReadonlySpan<u32> vector, u32& position)
static WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::BigInt>> deserialize_big_int_primitive(JS::VM& vm, ReadonlySpan<u32> vector, size_t& position)
{
auto string = TRY(deserialize_string_primitive(vm, vector, position));
auto string_view = TRY(Bindings::throw_dom_exception_if_needed(vm, [&string]() {
@ -689,21 +942,14 @@ WebIDL::ExceptionOr<SerializationRecord> structured_serialize_internal(JS::VM& v
return serializer.serialize(value);
}
WebIDL::ExceptionOr<JS::Value> structured_deserialize_impl(JS::VM& vm, ReadonlySpan<u32> serialized, JS::Realm& target_realm, SerializationMemory& memory)
{
// FIXME: Do the spec steps
Deserializer deserializer(vm, target_realm, serialized, memory);
TRY(deserializer.deserialize());
return deserializer.result();
}
// https://html.spec.whatwg.org/multipage/structured-data.html#structureddeserialize
WebIDL::ExceptionOr<JS::Value> structured_deserialize(JS::VM& vm, SerializationRecord const& serialized, JS::Realm& target_realm, Optional<SerializationMemory> memory)
WebIDL::ExceptionOr<JS::Value> structured_deserialize(JS::VM& vm, SerializationRecord const& serialized, JS::Realm& target_realm, Optional<DeserializationMemory> memory)
{
if (!memory.has_value())
memory = SerializationMemory {};
memory = DeserializationMemory { vm.heap() };
return structured_deserialize_impl(vm, serialized.span(), target_realm, *memory);
Deserializer deserializer(vm, target_realm, serialized.span(), *memory);
return deserializer.deserialize();
}
}

View file

@ -21,17 +21,14 @@
namespace Web::HTML {
using SerializationRecord = Vector<u32>;
struct SerializationRange {
u64 start = 0;
u64 end = 0;
};
using SerializationMemory = HashMap<JS::Handle<JS::Value>, SerializationRange>;
using SerializationMemory = HashMap<JS::Handle<JS::Value>, u32>;
using DeserializationMemory = JS::MarkedVector<JS::Value>;
WebIDL::ExceptionOr<SerializationRecord> structured_serialize(JS::VM& vm, JS::Value);
WebIDL::ExceptionOr<SerializationRecord> structured_serialize_for_storage(JS::VM& vm, JS::Value);
WebIDL::ExceptionOr<SerializationRecord> structured_serialize_internal(JS::VM& vm, JS::Value, bool for_storage, SerializationMemory&);
WebIDL::ExceptionOr<JS::Value> structured_deserialize(JS::VM& vm, SerializationRecord const& serialized, JS::Realm& target_realm, Optional<SerializationMemory>);
WebIDL::ExceptionOr<JS::Value> structured_deserialize(JS::VM& vm, SerializationRecord const& serialized, JS::Realm& target_realm, Optional<DeserializationMemory>);
// TODO: structured_[de]serialize_with_transfer

View file

@ -1057,7 +1057,7 @@ WebIDL::ExceptionOr<void> Window::window_post_message_steps(JS::Value message, W
// FIXME: Don't use a temporary execution context here.
auto& settings_object = Bindings::host_defined_environment_settings_object(target_realm);
auto temporary_execution_context = TemporaryExecutionContext { settings_object };
auto deserialize_record_or_error = structured_deserialize(vm(), serialize_with_transfer_result, target_realm, Optional<HTML::SerializationMemory> {});
auto deserialize_record_or_error = structured_deserialize(vm(), serialize_with_transfer_result, target_realm, Optional<HTML::DeserializationMemory> {});
// If this throws an exception, catch it, fire an event named messageerror at targetWindow, using MessageEvent,
// with the origin attribute initialized to origin and the source attribute initialized to source, and then return.

View file

@ -283,7 +283,7 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<UserTiming::PerformanceMeasure>> Performanc
auto record = TRY(HTML::structured_serialize(vm, start_or_measure_options_dictionary_object->detail));
// 2. Set entry's detail to the result of calling the StructuredDeserialize algorithm on record and the current realm.
detail = TRY(HTML::structured_deserialize(vm, record, realm, Optional<HTML::SerializationMemory> {}));
detail = TRY(HTML::structured_deserialize(vm, record, realm, Optional<HTML::DeserializationMemory> {}));
}
// 2. Otherwise, set it to null.

View file

@ -84,7 +84,7 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<PerformanceMark>> PerformanceMark::construc
auto record = TRY(HTML::structured_serialize(vm, mark_options.detail));
// 2. Set entry's detail to the result of calling the StructuredDeserialize algorithm on record and the current realm.
detail = TRY(HTML::structured_deserialize(vm, record, realm, Optional<HTML::SerializationMemory> {}));
detail = TRY(HTML::structured_deserialize(vm, record, realm, Optional<HTML::DeserializationMemory> {}));
}
// 2. Create a new PerformanceMark object (entry) with the current global object's realm.