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.