LibWeb: Add {de}serialization steps for ArrayBuffers

This commit is contained in:
Andrew Kaster 2023-09-11 18:06:43 -06:00 committed by Andreas Kling
parent a527f55768
commit 642802d339
Notes: sideshowbarker 2024-07-17 07:08:37 +09:00
3 changed files with 92 additions and 5 deletions

View file

@ -6,4 +6,5 @@ This is a String object
9007199254740991
1692748800000
/abc/gimsuy
[object ArrayBuffer]
ERROR: DataCloneError: Cannot serialize Symbol

View file

@ -10,6 +10,21 @@
println(structuredClone(Date.UTC(2023, 7, 23)));
println(structuredClone(/abc/gimsuy));
{
let arrayBuffer = new ArrayBuffer(6);
for (let i = 0; i < arrayBuffer.byteLength; ++i) {
arrayBuffer[i] = i;
}
let arrayClone = structuredClone(arrayBuffer);
for (let i = 0; i < arrayBuffer.byteLength; ++i) {
if (arrayBuffer[i] !== arrayBuffer[i]) {
println("FAILED");
}
}
// FIXME: This should print something like ArrayBuffer { byteLength: 6 }
println(arrayClone);
}
try {
structuredClone(Symbol("foo"));
println("FAILED")

View file

@ -10,6 +10,7 @@
#include <AK/String.h>
#include <AK/Vector.h>
#include <LibJS/Forward.h>
#include <LibJS/Runtime/ArrayBuffer.h>
#include <LibJS/Runtime/BigInt.h>
#include <LibJS/Runtime/BigIntObject.h>
#include <LibJS/Runtime/BooleanObject.h>
@ -70,6 +71,14 @@ enum ValueTag {
RegExpObject,
GrowableSharedArrayBuffer,
SharedArrayBuffer,
ResizeableArrayBuffer,
ArrayBuffer,
// TODO: Define many more types
// This tag or higher are understood to be errors
@ -82,9 +91,10 @@ enum ValueTag {
class Serializer {
public:
Serializer(JS::VM& vm, SerializationMemory& memory)
Serializer(JS::VM& vm, SerializationMemory& memory, bool for_storage)
: m_vm(vm)
, m_memory(memory)
, m_for_storage(for_storage)
{
}
@ -187,7 +197,12 @@ public:
TRY(serialize_string(m_serialized, TRY_OR_THROW_OOM(m_vm, String::from_deprecated_string(regexp_object.flags()))));
}
// 13 - 24: FIXME: Serialize other data types
// 13. Otherwise, if value has an [[ArrayBufferData]] internal slot, then:
else if (value.is_object() && is<JS::ArrayBuffer>(value.as_object())) {
TRY(serialize_array_buffer(m_serialized, static_cast<JS::ArrayBuffer&>(value.as_object())));
}
// 14 - 24: FIXME: Serialize other data types
else {
return throw_completion(WebIDL::DataCloneError::create(*m_vm.current_realm(), "Unsupported type"_fly_string));
}
@ -205,6 +220,7 @@ private:
JS::VM& m_vm;
SerializationMemory& m_memory; // JS value -> index
SerializationRecord m_serialized;
bool m_for_storage { false };
WebIDL::ExceptionOr<void> serialize_bytes(Vector<u32>& vector, ReadonlyBytes bytes)
{
@ -238,6 +254,57 @@ private:
TRY(serialize_string(vector, string));
return {};
}
WebIDL::ExceptionOr<void> serialize_array_buffer(Vector<u32>& vector, JS::ArrayBuffer const& array_buffer)
{
// 13. Otherwise, if value has an [[ArrayBufferData]] internal slot, then:
// FIXME: 1. If IsSharedArrayBuffer(value) is true, then:
if (false) {
// 1. If the current settings object's cross-origin isolated capability is false, then throw a "DataCloneError" DOMException.
// NOTE: This check is only needed when serializing (and not when deserializing) as the cross-origin isolated capability cannot change
// over time and a SharedArrayBuffer cannot leave an agent cluster.
if (current_settings_object().cross_origin_isolated_capability() == CanUseCrossOriginIsolatedAPIs::No)
return WebIDL::DataCloneError::create(*m_vm.current_realm(), "Cannot serialize SharedArrayBuffer when cross-origin isolated"_fly_string);
// 2. If forStorage is true, then throw a "DataCloneError" DOMException.
if (m_for_storage)
return WebIDL::DataCloneError::create(*m_vm.current_realm(), "Cannot serialize SharedArrayBuffer for storage"_fly_string);
// FIXME: 3. If value has an [[ArrayBufferMaxByteLength]] internal slot, then set serialized to { [[Type]]: "GrowableSharedArrayBuffer",
// [[ArrayBufferData]]: value.[[ArrayBufferData]], [[ArrayBufferByteLengthData]]: value.[[ArrayBufferByteLengthData]],
// [[ArrayBufferMaxByteLength]]: value.[[ArrayBufferMaxByteLength]], [[AgentCluster]]: the surrounding agent's agent cluster }.
// FIXME: 4. Otherwise, set serialized to { [[Type]]: "SharedArrayBuffer", [[ArrayBufferData]]: value.[[ArrayBufferData]],
// [[ArrayBufferByteLength]]: value.[[ArrayBufferByteLength]], [[AgentCluster]]: the surrounding agent's agent cluster }.
}
// 2. Otherwise:
else {
// 1. If IsDetachedBuffer(value) is true, then throw a "DataCloneError" DOMException.
if (array_buffer.is_detached())
return WebIDL::DataCloneError::create(*m_vm.current_realm(), "Cannot serialize detached ArrayBuffer"_fly_string);
// 2. Let size be value.[[ArrayBufferByteLength]].
auto size = array_buffer.byte_length();
// 3. Let dataCopy be ? CreateByteDataBlock(size).
// NOTE: This can throw a RangeError exception upon allocation failure.
auto data_copy = TRY(JS::create_byte_data_block(m_vm, size));
// 4. Perform CopyDataBlockBytes(dataCopy, 0, value.[[ArrayBufferData]], 0, size).
JS::copy_data_block_bytes(data_copy, 0, array_buffer.buffer(), 0, size);
// FIXME: 5. If value has an [[ArrayBufferMaxByteLength]] internal slot, then set serialized to { [[Type]]: "ResizableArrayBuffer",
// [[ArrayBufferData]]: dataCopy, [[ArrayBufferByteLength]]: size, [[ArrayBufferMaxByteLength]]: value.[[ArrayBufferMaxByteLength]] }.
if (false) {
}
// 6. Otherwise, set serialized to { [[Type]]: "ArrayBuffer", [[ArrayBufferData]]: dataCopy, [[ArrayBufferByteLength]]: size }.
else {
vector.append(ValueTag::ArrayBuffer);
TRY(serialize_bytes(vector, data_copy.bytes()));
}
}
return {};
}
};
class Deserializer {
@ -327,6 +394,12 @@ public:
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;
}
default:
m_error = "Unsupported type"_fly_string;
break;
@ -404,13 +477,11 @@ WebIDL::ExceptionOr<SerializationRecord> structured_serialize_for_storage(JS::VM
// https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializeinternal
WebIDL::ExceptionOr<SerializationRecord> structured_serialize_internal(JS::VM& vm, JS::Value value, bool for_storage, Optional<SerializationMemory> memory)
{
(void)for_storage;
// 1. If memory was not supplied, let memory be an empty map.
if (!memory.has_value())
memory = SerializationMemory {};
Serializer serializer(vm, *memory);
Serializer serializer(vm, *memory, for_storage);
return serializer.serialize(value);
}