diff --git a/Tests/LibWeb/Text/expected/HTML/StructuredClone-object-primitives.txt b/Tests/LibWeb/Text/expected/HTML/StructuredClone-object-primitives.txt index bcc689ffd6..3a21ddfe21 100644 --- a/Tests/LibWeb/Text/expected/HTML/StructuredClone-object-primitives.txt +++ b/Tests/LibWeb/Text/expected/HTML/StructuredClone-object-primitives.txt @@ -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 \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/HTML/Window-postMessage.txt b/Tests/LibWeb/Text/expected/HTML/Window-postMessage.txt index a3890c7d9f..d1b09cd76c 100644 --- a/Tests/LibWeb/Text/expected/HTML/Window-postMessage.txt +++ b/Tests/LibWeb/Text/expected/HTML/Window-postMessage.txt @@ -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 diff --git a/Tests/LibWeb/Text/input/HTML/StructuredClone-object-primitives.html b/Tests/LibWeb/Text/input/HTML/StructuredClone-object-primitives.html index b692902669..46b277beb7 100644 --- a/Tests/LibWeb/Text/input/HTML/StructuredClone-object-primitives.html +++ b/Tests/LibWeb/Text/input/HTML/StructuredClone-object-primitives.html @@ -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); diff --git a/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp b/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp index 75e21cc4b3..d1548fb98d 100644 --- a/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp +++ b/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp @@ -2,6 +2,7 @@ * Copyright (c) 2022, Daniel Ehrenberg * Copyright (c) 2022, Andrew Kaster * Copyright (c) 2023, Kenneth Myhra + * Copyright (c) 2023, Idan Horowitz * * SPDX-License-Identifier: BSD-2-Clause */ @@ -10,15 +11,18 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include +#include #include #include #include @@ -28,8 +32,6 @@ namespace Web::HTML { -static WebIDL::ExceptionOr structured_deserialize_impl(JS::VM& vm, ReadonlySpan 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 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 { 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(value.as_object())) { @@ -235,6 +243,22 @@ public: TRY(serialize_viewed_array_buffer(m_serialized, static_cast(value.as_object()))); } + // 15. Otherwise, if value has [[MapData]] internal slot, then: + else if (value.is_object() && is(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(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(value.as_object()) && !is(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(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(&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(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(value.as_object())) { + auto const& map = static_cast(value.as_object()); + // 1. Let copiedList be a new empty List. + Vector 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(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(&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(value.as_object())) { + auto const& set = static_cast(value.as_object()); + // 1. Let copiedList be a new empty List. + Vector copied_list; + copied_list.ensure_capacity(set.set_size()); + // 2. For each entry of value.[[SetData]]: + for (auto const& entry : static_cast(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(&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(&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) { vector.append(ValueTag::ArrayBufferView); - u64 const serialized_buffer_size = buffer_serialized.size(); - vector.append(bit_cast(&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(&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 v, SerializationMemory& serialization_memory) + Deserializer(JS::VM& vm, JS::Realm& target_realm, ReadonlySpan 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 deserialize() + // https://html.spec.whatwg.org/multipage/structured-data.html#structureddeserialize + WebIDL::ExceptionOr 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(m_vector[position++]))); - break; - } - case ValueTag::NumberPrimitive: { - u32 bits[2]; - bits[0] = m_vector[position++]; - bits[1] = m_vector[position++]; - double value = *bit_cast(&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(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(&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(&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(&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(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 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(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(&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(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(&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(&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(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 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(m_vector[position++]); - auto has_message = static_cast(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(m_serialized[m_position++]); + auto has_message = static_cast(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(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(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 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 m_vector; + ReadonlySpan m_serialized; + size_t m_position { 0 }; JS::MarkedVector m_memory; // Index -> JS value - Optional m_error; - SerializationMemory& m_serialization_memory; - static WebIDL::ExceptionOr deserialize_bytes(JS::VM& vm, ReadonlySpan 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 deserialize_bytes(JS::VM& vm, ReadonlySpan vector, size_t& position) { u32 size_bits[2]; size_bits[0] = vector[position++]; @@ -638,13 +891,13 @@ private: return bytes; } - static WebIDL::ExceptionOr deserialize_string(JS::VM& vm, ReadonlySpan vector, u32& position) + static WebIDL::ExceptionOr deserialize_string(JS::VM& vm, ReadonlySpan 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> deserialize_string_primitive(JS::VM& vm, ReadonlySpan vector, u32& position) + static WebIDL::ExceptionOr> deserialize_string_primitive(JS::VM& vm, ReadonlySpan vector, size_t& position) { auto bytes = TRY(deserialize_bytes(vm, vector, position)); @@ -653,7 +906,7 @@ private: })); } - static WebIDL::ExceptionOr> deserialize_big_int_primitive(JS::VM& vm, ReadonlySpan vector, u32& position) + static WebIDL::ExceptionOr> deserialize_big_int_primitive(JS::VM& vm, ReadonlySpan 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 structured_serialize_internal(JS::VM& v return serializer.serialize(value); } -WebIDL::ExceptionOr structured_deserialize_impl(JS::VM& vm, ReadonlySpan 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 structured_deserialize(JS::VM& vm, SerializationRecord const& serialized, JS::Realm& target_realm, Optional memory) +WebIDL::ExceptionOr structured_deserialize(JS::VM& vm, SerializationRecord const& serialized, JS::Realm& target_realm, Optional 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(); } } diff --git a/Userland/Libraries/LibWeb/HTML/StructuredSerialize.h b/Userland/Libraries/LibWeb/HTML/StructuredSerialize.h index f32cec69c9..7830cd9577 100644 --- a/Userland/Libraries/LibWeb/HTML/StructuredSerialize.h +++ b/Userland/Libraries/LibWeb/HTML/StructuredSerialize.h @@ -21,17 +21,14 @@ namespace Web::HTML { using SerializationRecord = Vector; -struct SerializationRange { - u64 start = 0; - u64 end = 0; -}; -using SerializationMemory = HashMap, SerializationRange>; +using SerializationMemory = HashMap, u32>; +using DeserializationMemory = JS::MarkedVector; WebIDL::ExceptionOr structured_serialize(JS::VM& vm, JS::Value); WebIDL::ExceptionOr structured_serialize_for_storage(JS::VM& vm, JS::Value); WebIDL::ExceptionOr structured_serialize_internal(JS::VM& vm, JS::Value, bool for_storage, SerializationMemory&); -WebIDL::ExceptionOr structured_deserialize(JS::VM& vm, SerializationRecord const& serialized, JS::Realm& target_realm, Optional); +WebIDL::ExceptionOr structured_deserialize(JS::VM& vm, SerializationRecord const& serialized, JS::Realm& target_realm, Optional); // TODO: structured_[de]serialize_with_transfer diff --git a/Userland/Libraries/LibWeb/HTML/Window.cpp b/Userland/Libraries/LibWeb/HTML/Window.cpp index b27ac83dea..dacb44e2f3 100644 --- a/Userland/Libraries/LibWeb/HTML/Window.cpp +++ b/Userland/Libraries/LibWeb/HTML/Window.cpp @@ -1057,7 +1057,7 @@ WebIDL::ExceptionOr 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 {}); + auto deserialize_record_or_error = structured_deserialize(vm(), serialize_with_transfer_result, target_realm, Optional {}); // 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. diff --git a/Userland/Libraries/LibWeb/HighResolutionTime/Performance.cpp b/Userland/Libraries/LibWeb/HighResolutionTime/Performance.cpp index 6827cf6770..8278e092af 100644 --- a/Userland/Libraries/LibWeb/HighResolutionTime/Performance.cpp +++ b/Userland/Libraries/LibWeb/HighResolutionTime/Performance.cpp @@ -283,7 +283,7 @@ WebIDL::ExceptionOr> 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 {})); + detail = TRY(HTML::structured_deserialize(vm, record, realm, Optional {})); } // 2. Otherwise, set it to null. diff --git a/Userland/Libraries/LibWeb/UserTiming/PerformanceMark.cpp b/Userland/Libraries/LibWeb/UserTiming/PerformanceMark.cpp index 5b931ae60b..d2d6f268cc 100644 --- a/Userland/Libraries/LibWeb/UserTiming/PerformanceMark.cpp +++ b/Userland/Libraries/LibWeb/UserTiming/PerformanceMark.cpp @@ -84,7 +84,7 @@ WebIDL::ExceptionOr> 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 {})); + detail = TRY(HTML::structured_deserialize(vm, record, realm, Optional {})); } // 2. Create a new PerformanceMark object (entry) with the current global object's realm.