From 5ae9b2fdaf4c0c66ad440eaf16d9e262ad693c76 Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Tue, 12 Sep 2023 16:56:35 -0600 Subject: [PATCH] LibWeb: Add {de}serialization steps for TypedArrayBuffers and DataViews --- .../StructuredClone-array-buffer-views.txt | 12 +++ .../StructuredClone-array-buffer-views.html | 51 ++++++++++ .../LibWeb/HTML/StructuredSerialize.cpp | 99 ++++++++++++++++++- 3 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 Tests/LibWeb/Text/expected/HTML/StructuredClone-array-buffer-views.txt create mode 100644 Tests/LibWeb/Text/input/HTML/StructuredClone-array-buffer-views.html diff --git a/Tests/LibWeb/Text/expected/HTML/StructuredClone-array-buffer-views.txt b/Tests/LibWeb/Text/expected/HTML/StructuredClone-array-buffer-views.txt new file mode 100644 index 0000000000..ecec036968 --- /dev/null +++ b/Tests/LibWeb/Text/expected/HTML/StructuredClone-array-buffer-views.txt @@ -0,0 +1,12 @@ +Uint8Array 30,40,50,60 +Uint8ClampedArray 30,40,50,60 +Uint16Array 30,40,50,60 +Uint32Array 30,40,50,60 +Int8Array 30,40,50,60 +Int16Array 30,40,50,60 +Int32Array 30,40,50,60 +Float32Array 30,40,50,60 +Float64Array 30,40,50,60 +BigUint64Array 30,40,50,60 +BigInt64Array 30,40,50,60 +DataView [object DataView] diff --git a/Tests/LibWeb/Text/input/HTML/StructuredClone-array-buffer-views.html b/Tests/LibWeb/Text/input/HTML/StructuredClone-array-buffer-views.html new file mode 100644 index 0000000000..7615a1789b --- /dev/null +++ b/Tests/LibWeb/Text/input/HTML/StructuredClone-array-buffer-views.html @@ -0,0 +1,51 @@ + + diff --git a/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp b/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp index 4cf45babc1..610a96ce5a 100644 --- a/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp +++ b/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp @@ -14,11 +14,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -81,6 +83,8 @@ enum ValueTag { ArrayBuffer, + ArrayBufferView, + // TODO: Define many more types // This tag or higher are understood to be errors @@ -204,7 +208,14 @@ public: TRY(serialize_array_buffer(m_serialized, static_cast(value.as_object()))); } - // 14 - 24: FIXME: Serialize other data types + // 14. Otherwise, if value has a [[ViewedArrayBuffer]] internal slot, then: + else if (value.is_object() && is(value.as_object())) { + TRY(serialize_viewed_array_buffer(m_serialized, static_cast(value.as_object()))); + } else if (value.is_object() && is(value.as_object())) { + TRY(serialize_viewed_array_buffer(m_serialized, static_cast(value.as_object()))); + } + + // 15 - 24: FIXME: Serialize other data types else { return throw_completion(WebIDL::DataCloneError::create(*m_vm.current_realm(), "Unsupported type"_fly_string)); } @@ -245,6 +256,11 @@ private: return {}; } + WebIDL::ExceptionOr serialize_string(Vector& vector, DeprecatedFlyString const& string) + { + return serialize_bytes(vector, string.view().bytes()); + } + WebIDL::ExceptionOr serialize_string(Vector& vector, String const& string) { return serialize_bytes(vector, { string.code_points().bytes(), string.code_points().byte_length() }); @@ -307,6 +323,54 @@ private: } return {}; } + + template ViewType> + WebIDL::ExceptionOr serialize_viewed_array_buffer(Vector& vector, ViewType const& view) + { + // 14. Otherwise, if value has a [[ViewedArrayBuffer]] internal slot, then: + + // FIXME: 1. If IsArrayBufferViewOutOfBounds(value) is true, then throw a "DataCloneError" DOMException. + + // 2. Let buffer be the value of value's [[ViewedArrayBuffer]] internal slot. + auto* buffer = view.viewed_array_buffer(); + + // 3. Let bufferSerialized be ? StructuredSerializeInternal(buffer, forStorage, memory). + auto buffer_serialized = TRY(structured_serialize_internal(m_vm, JS::Value(buffer), m_for_storage, m_memory)); + + // 4. Assert: bufferSerialized.[[Type]] is "ArrayBuffer", "ResizableArrayBuffer", "SharedArrayBuffer", or "GrowableSharedArrayBuffer". + // NOTE: We currently only implement this for ArrayBuffer + VERIFY(buffer_serialized[0] == ValueTag::ArrayBuffer); + + // 5. If value has a [[DataView]] internal slot, then set serialized to { [[Type]]: "ArrayBufferView", [[Constructor]]: "DataView", + // [[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()); + vector.append(view.byte_offset()); + } + + // 6. Otherwise: + else { + // 1. Assert: value has a [[TypedArrayName]] internal slot. + // NOTE: Handled by constexpr check and template constraints + // 2. Set serialized to { [[Type]]: "ArrayBufferView", [[Constructor]]: value.[[TypedArrayName]], + // [[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()); + vector.append(view.byte_offset()); + vector.append(view.array_length()); + } + return {}; + } }; class Deserializer { @@ -404,6 +468,39 @@ public: 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++]; + + 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; +#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 +#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; + } default: m_error = "Unsupported type"_fly_string; break;