mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-29 04:09:13 +00:00
LibWeb: Move array buffer/view serializers above their users
Now that these serializers are internal to StructuredSerialize.cpp, let's put them above Serializer so they don't have to be forward- declared and explicitly instantiated.
This commit is contained in:
parent
64abc6101d
commit
ed71db45e7
Notes:
github-actions[bot]
2025-07-18 14:10:38 +00:00
Author: https://github.com/trflynn89
Commit: ed71db45e7
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5492
Reviewed-by: https://github.com/ADKaster
Reviewed-by: https://github.com/shannonbooth
1 changed files with 124 additions and 130 deletions
|
@ -118,10 +118,132 @@ static ErrorType error_name_to_type(String const& name)
|
||||||
return Error;
|
return Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
static WebIDL::ExceptionOr<void> serialize_array_buffer(JS::VM&, TransferDataEncoder&, JS::ArrayBuffer const&, bool for_storage);
|
// https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializeinternal
|
||||||
|
static WebIDL::ExceptionOr<void> serialize_array_buffer(JS::VM& vm, TransferDataEncoder& data_holder, JS::ArrayBuffer const& array_buffer, bool for_storage)
|
||||||
|
{
|
||||||
|
// 13. Otherwise, if value has an [[ArrayBufferData]] internal slot, then:
|
||||||
|
|
||||||
|
// 1. If IsSharedArrayBuffer(value) is true, then:
|
||||||
|
if (array_buffer.is_shared_array_buffer()) {
|
||||||
|
// 1. If the current principal 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_principal_settings_object().cross_origin_isolated_capability() == CanUseCrossOriginIsolatedAPIs::No)
|
||||||
|
return WebIDL::DataCloneError::create(*vm.current_realm(), "Cannot serialize SharedArrayBuffer when cross-origin isolated"_string);
|
||||||
|
|
||||||
|
// 2. If forStorage is true, then throw a "DataCloneError" DOMException.
|
||||||
|
if (for_storage)
|
||||||
|
return WebIDL::DataCloneError::create(*vm.current_realm(), "Cannot serialize SharedArrayBuffer for storage"_string);
|
||||||
|
|
||||||
|
if (!array_buffer.is_fixed_length()) {
|
||||||
|
// 3. If value has an [[ArrayBufferMaxByteLength]] internal slot, then set serialized to { [[Type]]: "GrowableSharedArrayBuffer",
|
||||||
|
// [[ArrayBufferData]]: value.[[ArrayBufferData]], [[ArrayBufferByteLengthData]]: value.[[ArrayBufferByteLengthData]],
|
||||||
|
// [[ArrayBufferMaxByteLength]]: value.[[ArrayBufferMaxByteLength]],
|
||||||
|
// FIXME: [[AgentCluster]]: the surrounding agent's agent cluster }.
|
||||||
|
data_holder.encode(ValueTag::GrowableSharedArrayBuffer);
|
||||||
|
data_holder.encode(array_buffer.buffer());
|
||||||
|
data_holder.encode(array_buffer.max_byte_length());
|
||||||
|
} else {
|
||||||
|
// 4. Otherwise, set serialized to { [[Type]]: "SharedArrayBuffer", [[ArrayBufferData]]: value.[[ArrayBufferData]],
|
||||||
|
// [[ArrayBufferByteLength]]: value.[[ArrayBufferByteLength]],
|
||||||
|
// FIXME: [[AgentCluster]]: the surrounding agent's agent cluster }.
|
||||||
|
data_holder.encode(ValueTag::SharedArrayBuffer);
|
||||||
|
data_holder.encode(array_buffer.buffer());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 2. Otherwise:
|
||||||
|
else {
|
||||||
|
// 1. If IsDetachedBuffer(value) is true, then throw a "DataCloneError" DOMException.
|
||||||
|
if (array_buffer.is_detached())
|
||||||
|
return WebIDL::DataCloneError::create(*vm.current_realm(), "Cannot serialize detached ArrayBuffer"_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(vm, size));
|
||||||
|
|
||||||
|
// 4. Perform CopyDataBlockBytes(dataCopy, 0, value.[[ArrayBufferData]], 0, size).
|
||||||
|
JS::copy_data_block_bytes(data_copy.buffer(), 0, array_buffer.buffer(), 0, size);
|
||||||
|
|
||||||
|
// 5. If value has an [[ArrayBufferMaxByteLength]] internal slot, then set serialized to { [[Type]]: "ResizableArrayBuffer",
|
||||||
|
// [[ArrayBufferData]]: dataCopy, [[ArrayBufferByteLength]]: size, [[ArrayBufferMaxByteLength]]: value.[[ArrayBufferMaxByteLength]] }.
|
||||||
|
if (!array_buffer.is_fixed_length()) {
|
||||||
|
data_holder.encode(ValueTag::ResizeableArrayBuffer);
|
||||||
|
data_holder.encode(data_copy.buffer());
|
||||||
|
data_holder.encode(array_buffer.max_byte_length());
|
||||||
|
}
|
||||||
|
// 6. Otherwise, set serialized to { [[Type]]: "ArrayBuffer", [[ArrayBufferData]]: dataCopy, [[ArrayBufferByteLength]]: size }.
|
||||||
|
else {
|
||||||
|
data_holder.encode(ValueTag::ArrayBuffer);
|
||||||
|
data_holder.encode(data_copy.buffer());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializeinternal
|
||||||
template<OneOf<JS::TypedArrayBase, JS::DataView> ViewType>
|
template<OneOf<JS::TypedArrayBase, JS::DataView> ViewType>
|
||||||
static WebIDL::ExceptionOr<void> serialize_viewed_array_buffer(JS::VM&, TransferDataEncoder&, ViewType const&, bool for_storage, SerializationMemory&);
|
static WebIDL::ExceptionOr<void> serialize_viewed_array_buffer(JS::VM& vm, TransferDataEncoder& data_holder, ViewType const& view, bool for_storage, SerializationMemory& memory)
|
||||||
|
{
|
||||||
|
// 14. Otherwise, if value has a [[ViewedArrayBuffer]] internal slot, then:
|
||||||
|
|
||||||
|
auto view_record = [&]() {
|
||||||
|
if constexpr (IsSame<ViewType, JS::DataView>)
|
||||||
|
return JS::make_data_view_with_buffer_witness_record(view, JS::ArrayBuffer::Order::SeqCst);
|
||||||
|
else
|
||||||
|
return JS::make_typed_array_with_buffer_witness_record(view, JS::ArrayBuffer::Order::SeqCst);
|
||||||
|
}();
|
||||||
|
|
||||||
|
// 1. If IsArrayBufferViewOutOfBounds(value) is true, then throw a "DataCloneError" DOMException.
|
||||||
|
if constexpr (IsSame<ViewType, JS::DataView>) {
|
||||||
|
if (JS::is_view_out_of_bounds(view_record))
|
||||||
|
return WebIDL::DataCloneError::create(*vm.current_realm(), MUST(String::formatted(JS::ErrorType::BufferOutOfBounds.message(), "DataView"sv)));
|
||||||
|
} else {
|
||||||
|
if (JS::is_typed_array_out_of_bounds(view_record))
|
||||||
|
return WebIDL::DataCloneError::create(*vm.current_realm(), MUST(String::formatted(JS::ErrorType::BufferOutOfBounds.message(), "TypedArray"sv)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Let buffer be the value of value's [[ViewedArrayBuffer]] internal slot.
|
||||||
|
JS::Value buffer = view.viewed_array_buffer();
|
||||||
|
|
||||||
|
// 3. Let bufferSerialized be ? StructuredSerializeInternal(buffer, forStorage, memory).
|
||||||
|
auto buffer_serialized = TRY(structured_serialize_internal(vm, buffer, for_storage, memory));
|
||||||
|
|
||||||
|
// 4. Assert: bufferSerialized.[[Type]] is "ArrayBuffer", "ResizableArrayBuffer", "SharedArrayBuffer", or "GrowableSharedArrayBuffer".
|
||||||
|
// NOTE: Object reference + memory check is required when ArrayBuffer is transferred.
|
||||||
|
auto tag = TransferDataDecoder { buffer_serialized }.decode<ValueTag>();
|
||||||
|
|
||||||
|
VERIFY(first_is_one_of(tag, ValueTag::ArrayBuffer, ValueTag::ResizeableArrayBuffer, ValueTag::SharedArrayBuffer, ValueTag::GrowableSharedArrayBuffer)
|
||||||
|
|| (tag == ValueTag::ObjectReference && memory.contains(buffer)));
|
||||||
|
|
||||||
|
// 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<ViewType, JS::DataView>) {
|
||||||
|
data_holder.encode(ValueTag::ArrayBufferView);
|
||||||
|
data_holder.append(move(buffer_serialized)); // [[ArrayBufferSerialized]]
|
||||||
|
data_holder.encode("DataView"_string); // [[Constructor]]
|
||||||
|
data_holder.encode(JS::get_view_byte_length(view_record));
|
||||||
|
data_holder.encode(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]] }.
|
||||||
|
data_holder.encode(ValueTag::ArrayBufferView);
|
||||||
|
data_holder.append(move(buffer_serialized)); // [[ArrayBufferSerialized]]
|
||||||
|
data_holder.encode(view.element_name().to_string()); // [[Constructor]]
|
||||||
|
data_holder.encode(JS::typed_array_byte_length(view_record));
|
||||||
|
data_holder.encode(view.byte_offset());
|
||||||
|
data_holder.encode(JS::typed_array_length(view_record));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
// Serializing and deserializing are each two passes:
|
// Serializing and deserializing are each two passes:
|
||||||
// 1. Fill up the memory with all the values, but without translating references
|
// 1. Fill up the memory with all the values, but without translating references
|
||||||
|
@ -445,134 +567,6 @@ private:
|
||||||
bool m_for_storage { false };
|
bool m_for_storage { false };
|
||||||
};
|
};
|
||||||
|
|
||||||
WebIDL::ExceptionOr<void> serialize_array_buffer(JS::VM& vm, TransferDataEncoder& data_holder, JS::ArrayBuffer const& array_buffer, bool for_storage)
|
|
||||||
{
|
|
||||||
// 13. Otherwise, if value has an [[ArrayBufferData]] internal slot, then:
|
|
||||||
|
|
||||||
// 1. If IsSharedArrayBuffer(value) is true, then:
|
|
||||||
if (array_buffer.is_shared_array_buffer()) {
|
|
||||||
// 1. If the current principal 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_principal_settings_object().cross_origin_isolated_capability() == CanUseCrossOriginIsolatedAPIs::No)
|
|
||||||
return WebIDL::DataCloneError::create(*vm.current_realm(), "Cannot serialize SharedArrayBuffer when cross-origin isolated"_string);
|
|
||||||
|
|
||||||
// 2. If forStorage is true, then throw a "DataCloneError" DOMException.
|
|
||||||
if (for_storage)
|
|
||||||
return WebIDL::DataCloneError::create(*vm.current_realm(), "Cannot serialize SharedArrayBuffer for storage"_string);
|
|
||||||
|
|
||||||
if (!array_buffer.is_fixed_length()) {
|
|
||||||
// 3. If value has an [[ArrayBufferMaxByteLength]] internal slot, then set serialized to { [[Type]]: "GrowableSharedArrayBuffer",
|
|
||||||
// [[ArrayBufferData]]: value.[[ArrayBufferData]], [[ArrayBufferByteLengthData]]: value.[[ArrayBufferByteLengthData]],
|
|
||||||
// [[ArrayBufferMaxByteLength]]: value.[[ArrayBufferMaxByteLength]],
|
|
||||||
// FIXME: [[AgentCluster]]: the surrounding agent's agent cluster }.
|
|
||||||
data_holder.encode(ValueTag::GrowableSharedArrayBuffer);
|
|
||||||
data_holder.encode(array_buffer.buffer());
|
|
||||||
data_holder.encode(array_buffer.max_byte_length());
|
|
||||||
} else {
|
|
||||||
// 4. Otherwise, set serialized to { [[Type]]: "SharedArrayBuffer", [[ArrayBufferData]]: value.[[ArrayBufferData]],
|
|
||||||
// [[ArrayBufferByteLength]]: value.[[ArrayBufferByteLength]],
|
|
||||||
// FIXME: [[AgentCluster]]: the surrounding agent's agent cluster }.
|
|
||||||
data_holder.encode(ValueTag::SharedArrayBuffer);
|
|
||||||
data_holder.encode(array_buffer.buffer());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 2. Otherwise:
|
|
||||||
else {
|
|
||||||
// 1. If IsDetachedBuffer(value) is true, then throw a "DataCloneError" DOMException.
|
|
||||||
if (array_buffer.is_detached())
|
|
||||||
return WebIDL::DataCloneError::create(*vm.current_realm(), "Cannot serialize detached ArrayBuffer"_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(vm, size));
|
|
||||||
|
|
||||||
// 4. Perform CopyDataBlockBytes(dataCopy, 0, value.[[ArrayBufferData]], 0, size).
|
|
||||||
JS::copy_data_block_bytes(data_copy.buffer(), 0, array_buffer.buffer(), 0, size);
|
|
||||||
|
|
||||||
// 5. If value has an [[ArrayBufferMaxByteLength]] internal slot, then set serialized to { [[Type]]: "ResizableArrayBuffer",
|
|
||||||
// [[ArrayBufferData]]: dataCopy, [[ArrayBufferByteLength]]: size, [[ArrayBufferMaxByteLength]]: value.[[ArrayBufferMaxByteLength]] }.
|
|
||||||
if (!array_buffer.is_fixed_length()) {
|
|
||||||
data_holder.encode(ValueTag::ResizeableArrayBuffer);
|
|
||||||
data_holder.encode(data_copy.buffer());
|
|
||||||
data_holder.encode(array_buffer.max_byte_length());
|
|
||||||
}
|
|
||||||
// 6. Otherwise, set serialized to { [[Type]]: "ArrayBuffer", [[ArrayBufferData]]: dataCopy, [[ArrayBufferByteLength]]: size }.
|
|
||||||
else {
|
|
||||||
data_holder.encode(ValueTag::ArrayBuffer);
|
|
||||||
data_holder.encode(data_copy.buffer());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
template<OneOf<JS::TypedArrayBase, JS::DataView> ViewType>
|
|
||||||
WebIDL::ExceptionOr<void> serialize_viewed_array_buffer(JS::VM& vm, TransferDataEncoder& data_holder, ViewType const& view, bool for_storage, SerializationMemory& memory)
|
|
||||||
{
|
|
||||||
// 14. Otherwise, if value has a [[ViewedArrayBuffer]] internal slot, then:
|
|
||||||
|
|
||||||
auto view_record = [&]() {
|
|
||||||
if constexpr (IsSame<ViewType, JS::DataView>)
|
|
||||||
return JS::make_data_view_with_buffer_witness_record(view, JS::ArrayBuffer::Order::SeqCst);
|
|
||||||
else
|
|
||||||
return JS::make_typed_array_with_buffer_witness_record(view, JS::ArrayBuffer::Order::SeqCst);
|
|
||||||
}();
|
|
||||||
|
|
||||||
// 1. If IsArrayBufferViewOutOfBounds(value) is true, then throw a "DataCloneError" DOMException.
|
|
||||||
if constexpr (IsSame<ViewType, JS::DataView>) {
|
|
||||||
if (JS::is_view_out_of_bounds(view_record))
|
|
||||||
return WebIDL::DataCloneError::create(*vm.current_realm(), MUST(String::formatted(JS::ErrorType::BufferOutOfBounds.message(), "DataView"sv)));
|
|
||||||
} else {
|
|
||||||
if (JS::is_typed_array_out_of_bounds(view_record))
|
|
||||||
return WebIDL::DataCloneError::create(*vm.current_realm(), MUST(String::formatted(JS::ErrorType::BufferOutOfBounds.message(), "TypedArray"sv)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Let buffer be the value of value's [[ViewedArrayBuffer]] internal slot.
|
|
||||||
JS::Value buffer = view.viewed_array_buffer();
|
|
||||||
|
|
||||||
// 3. Let bufferSerialized be ? StructuredSerializeInternal(buffer, forStorage, memory).
|
|
||||||
auto buffer_serialized = TRY(structured_serialize_internal(vm, buffer, for_storage, memory));
|
|
||||||
|
|
||||||
// 4. Assert: bufferSerialized.[[Type]] is "ArrayBuffer", "ResizableArrayBuffer", "SharedArrayBuffer", or "GrowableSharedArrayBuffer".
|
|
||||||
// NOTE: Object reference + memory check is required when ArrayBuffer is transferred.
|
|
||||||
auto tag = TransferDataDecoder { buffer_serialized }.decode<ValueTag>();
|
|
||||||
|
|
||||||
VERIFY(first_is_one_of(tag, ValueTag::ArrayBuffer, ValueTag::ResizeableArrayBuffer, ValueTag::SharedArrayBuffer, ValueTag::GrowableSharedArrayBuffer)
|
|
||||||
|| (tag == ValueTag::ObjectReference && memory.contains(buffer)));
|
|
||||||
|
|
||||||
// 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<ViewType, JS::DataView>) {
|
|
||||||
data_holder.encode(ValueTag::ArrayBufferView);
|
|
||||||
data_holder.append(move(buffer_serialized)); // [[ArrayBufferSerialized]]
|
|
||||||
data_holder.encode("DataView"_string); // [[Constructor]]
|
|
||||||
data_holder.encode(JS::get_view_byte_length(view_record));
|
|
||||||
data_holder.encode(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]] }.
|
|
||||||
data_holder.encode(ValueTag::ArrayBufferView);
|
|
||||||
data_holder.append(move(buffer_serialized)); // [[ArrayBufferSerialized]]
|
|
||||||
data_holder.encode(view.element_name().to_string()); // [[Constructor]]
|
|
||||||
data_holder.encode(JS::typed_array_byte_length(view_record));
|
|
||||||
data_holder.encode(view.byte_offset());
|
|
||||||
data_holder.encode(JS::typed_array_length(view_record));
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
template WebIDL::ExceptionOr<void> serialize_viewed_array_buffer(JS::VM&, TransferDataEncoder&, JS::TypedArrayBase const&, bool, SerializationMemory&);
|
|
||||||
template WebIDL::ExceptionOr<void> serialize_viewed_array_buffer(JS::VM&, TransferDataEncoder&, JS::DataView const&, bool, SerializationMemory&);
|
|
||||||
|
|
||||||
class Deserializer {
|
class Deserializer {
|
||||||
public:
|
public:
|
||||||
Deserializer(JS::VM& vm, TransferDataDecoder& serialized, JS::Realm& target_realm, DeserializationMemory& memory)
|
Deserializer(JS::VM& vm, TransferDataDecoder& serialized, JS::Realm& target_realm, DeserializationMemory& memory)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue