mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-22 17:01:54 +00:00
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.
1398 lines
65 KiB
C++
1398 lines
65 KiB
C++
/*
|
|
* Copyright (c) 2022, Daniel Ehrenberg <dan@littledan.dev>
|
|
* Copyright (c) 2022, Andrew Kaster <akaster@serenityos.org>
|
|
* Copyright (c) 2023-2024, Kenneth Myhra <kennethmyhra@serenityos.org>
|
|
* Copyright (c) 2023, Idan Horowitz <idan.horowitz@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/StdLibExtras.h>
|
|
#include <AK/String.h>
|
|
#include <LibIPC/File.h>
|
|
#include <LibJS/Runtime/Array.h>
|
|
#include <LibJS/Runtime/ArrayBuffer.h>
|
|
#include <LibJS/Runtime/BigInt.h>
|
|
#include <LibJS/Runtime/BigIntObject.h>
|
|
#include <LibJS/Runtime/BooleanObject.h>
|
|
#include <LibJS/Runtime/DataView.h>
|
|
#include <LibJS/Runtime/Date.h>
|
|
#include <LibJS/Runtime/Map.h>
|
|
#include <LibJS/Runtime/NumberObject.h>
|
|
#include <LibJS/Runtime/PrimitiveString.h>
|
|
#include <LibJS/Runtime/RegExpObject.h>
|
|
#include <LibJS/Runtime/Set.h>
|
|
#include <LibJS/Runtime/StringObject.h>
|
|
#include <LibJS/Runtime/TypedArray.h>
|
|
#include <LibJS/Runtime/VM.h>
|
|
#include <LibWeb/Bindings/DOMExceptionPrototype.h>
|
|
#include <LibWeb/Bindings/DOMMatrixPrototype.h>
|
|
#include <LibWeb/Bindings/DOMMatrixReadOnlyPrototype.h>
|
|
#include <LibWeb/Bindings/DOMPointPrototype.h>
|
|
#include <LibWeb/Bindings/DOMPointReadOnlyPrototype.h>
|
|
#include <LibWeb/Bindings/DOMQuadPrototype.h>
|
|
#include <LibWeb/Bindings/DOMRectPrototype.h>
|
|
#include <LibWeb/Bindings/DOMRectReadOnlyPrototype.h>
|
|
#include <LibWeb/Bindings/FileListPrototype.h>
|
|
#include <LibWeb/Bindings/FilePrototype.h>
|
|
#include <LibWeb/Bindings/ImageBitmapPrototype.h>
|
|
#include <LibWeb/Bindings/Intrinsics.h>
|
|
#include <LibWeb/Bindings/MessagePortPrototype.h>
|
|
#include <LibWeb/Bindings/ReadableStreamPrototype.h>
|
|
#include <LibWeb/Bindings/Serializable.h>
|
|
#include <LibWeb/Bindings/Transferable.h>
|
|
#include <LibWeb/Bindings/TransformStreamPrototype.h>
|
|
#include <LibWeb/Bindings/WritableStreamPrototype.h>
|
|
#include <LibWeb/Crypto/CryptoKey.h>
|
|
#include <LibWeb/FileAPI/Blob.h>
|
|
#include <LibWeb/FileAPI/File.h>
|
|
#include <LibWeb/FileAPI/FileList.h>
|
|
#include <LibWeb/Geometry/DOMMatrix.h>
|
|
#include <LibWeb/Geometry/DOMMatrixReadOnly.h>
|
|
#include <LibWeb/Geometry/DOMPoint.h>
|
|
#include <LibWeb/Geometry/DOMPointReadOnly.h>
|
|
#include <LibWeb/Geometry/DOMQuad.h>
|
|
#include <LibWeb/Geometry/DOMRect.h>
|
|
#include <LibWeb/Geometry/DOMRectReadOnly.h>
|
|
#include <LibWeb/HTML/ImageBitmap.h>
|
|
#include <LibWeb/HTML/ImageData.h>
|
|
#include <LibWeb/HTML/MessagePort.h>
|
|
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
|
|
#include <LibWeb/HTML/StructuredSerialize.h>
|
|
#include <LibWeb/Streams/ReadableStream.h>
|
|
#include <LibWeb/Streams/TransformStream.h>
|
|
#include <LibWeb/Streams/WritableStream.h>
|
|
#include <LibWeb/WebIDL/DOMException.h>
|
|
|
|
namespace Web::HTML {
|
|
|
|
enum class ValueTag : u8 {
|
|
Empty, // Unused, for ease of catching bugs.
|
|
|
|
UndefinedPrimitive,
|
|
NullPrimitive,
|
|
BooleanPrimitive,
|
|
NumberPrimitive,
|
|
StringPrimitive,
|
|
BigIntPrimitive,
|
|
|
|
BooleanObject,
|
|
NumberObject,
|
|
StringObject,
|
|
BigIntObject,
|
|
DateObject,
|
|
RegExpObject,
|
|
MapObject,
|
|
SetObject,
|
|
ArrayObject,
|
|
ErrorObject,
|
|
Object,
|
|
ObjectReference,
|
|
|
|
GrowableSharedArrayBuffer,
|
|
SharedArrayBuffer,
|
|
ResizeableArrayBuffer,
|
|
ArrayBuffer,
|
|
ArrayBufferView,
|
|
|
|
SerializableObject,
|
|
|
|
// TODO: Define many more types
|
|
};
|
|
|
|
enum ErrorType {
|
|
Error,
|
|
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
|
|
ClassName,
|
|
JS_ENUMERATE_NATIVE_ERRORS
|
|
#undef __JS_ENUMERATE
|
|
};
|
|
|
|
static ErrorType error_name_to_type(String const& name)
|
|
{
|
|
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
|
|
if (name == #ClassName##sv) \
|
|
return ErrorType::ClassName;
|
|
JS_ENUMERATE_NATIVE_ERRORS
|
|
#undef __JS_ENUMERATE
|
|
return Error;
|
|
}
|
|
|
|
// 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>
|
|
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:
|
|
// 1. Fill up the memory with all the values, but without translating references
|
|
// 2. Translate all the references into the appropriate form
|
|
class Serializer {
|
|
public:
|
|
Serializer(JS::VM& vm, SerializationMemory& memory, bool for_storage)
|
|
: m_vm(vm)
|
|
, m_memory(memory)
|
|
, m_next_id(memory.size())
|
|
, m_for_storage(for_storage)
|
|
{
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializeinternal
|
|
// https://whatpr.org/html/9893/structured-data.html#structuredserializeinternal
|
|
WebIDL::ExceptionOr<SerializationRecord> serialize(JS::Value value)
|
|
{
|
|
TransferDataEncoder serialized;
|
|
|
|
// 2. If memory[value] exists, then return memory[value].
|
|
if (m_memory.contains(value)) {
|
|
serialized.encode(ValueTag::ObjectReference);
|
|
serialized.encode(m_memory.get(value).value());
|
|
return serialized.take_buffer().take_data();
|
|
}
|
|
|
|
// 3. Let deep be false.
|
|
auto deep = false;
|
|
|
|
// 4. If value is undefined, null, a Boolean, a Number, a BigInt, or a String, then return { [[Type]]: "primitive", [[Value]]: value }.
|
|
bool return_primitive_type = true;
|
|
|
|
if (value.is_undefined()) {
|
|
serialized.encode(ValueTag::UndefinedPrimitive);
|
|
} else if (value.is_null()) {
|
|
serialized.encode(ValueTag::NullPrimitive);
|
|
} else if (value.is_boolean()) {
|
|
serialized.encode(ValueTag::BooleanPrimitive);
|
|
serialized.encode(value.as_bool());
|
|
} else if (value.is_number()) {
|
|
serialized.encode(ValueTag::NumberPrimitive);
|
|
serialized.encode(value.as_double());
|
|
} else if (value.is_bigint()) {
|
|
serialized.encode(ValueTag::BigIntPrimitive);
|
|
serialized.encode(MUST(value.as_bigint().big_integer().to_base(10)));
|
|
} else if (value.is_string()) {
|
|
serialized.encode(ValueTag::StringPrimitive);
|
|
serialized.encode(value.as_string().utf8_string());
|
|
} else {
|
|
return_primitive_type = false;
|
|
}
|
|
|
|
if (return_primitive_type)
|
|
return serialized.take_buffer().take_data();
|
|
|
|
// 5. If value is a Symbol, then throw a "DataCloneError" DOMException.
|
|
if (value.is_symbol())
|
|
return WebIDL::DataCloneError::create(*m_vm.current_realm(), "Cannot serialize Symbol"_string);
|
|
|
|
// 6. Let serialized be an uninitialized value.
|
|
// NOTE: We created the serialized value above.
|
|
|
|
if (value.is_object()) {
|
|
auto const& object = value.as_object();
|
|
|
|
// 7. If value has a [[BooleanData]] internal slot, then set serialized to { [[Type]]: "Boolean", [[BooleanData]]: value.[[BooleanData]] }.
|
|
if (auto const* boolean_object = as_if<JS::BooleanObject>(object)) {
|
|
serialized.encode(ValueTag::BooleanObject);
|
|
serialized.encode(boolean_object->boolean());
|
|
}
|
|
|
|
// 8. Otherwise, if value has a [[NumberData]] internal slot, then set serialized to { [[Type]]: "Number", [[NumberData]]: value.[[NumberData]] }.
|
|
else if (auto const* number_object = as_if<JS::NumberObject>(object)) {
|
|
serialized.encode(ValueTag::NumberObject);
|
|
serialized.encode(number_object->number());
|
|
}
|
|
|
|
// 9. Otherwise, if value has a [[BigIntData]] internal slot, then set serialized to { [[Type]]: "BigInt", [[BigIntData]]: value.[[BigIntData]] }.
|
|
else if (auto const* big_int_object = as_if<JS::BigIntObject>(object)) {
|
|
serialized.encode(ValueTag::BigIntObject);
|
|
serialized.encode(MUST(big_int_object->bigint().big_integer().to_base(10)));
|
|
}
|
|
|
|
// 10. Otherwise, if value has a [[StringData]] internal slot, then set serialized to { [[Type]]: "String", [[StringData]]: value.[[StringData]] }.
|
|
else if (auto const* string_object = as_if<JS::StringObject>(object)) {
|
|
serialized.encode(ValueTag::StringObject);
|
|
serialized.encode(string_object->primitive_string().utf8_string());
|
|
}
|
|
|
|
// 11. Otherwise, if value has a [[DateValue]] internal slot, then set serialized to { [[Type]]: "Date", [[DateValue]]: value.[[DateValue]] }.
|
|
else if (auto const* date = as_if<JS::Date>(object)) {
|
|
serialized.encode(ValueTag::DateObject);
|
|
serialized.encode(date->date_value());
|
|
}
|
|
|
|
// 12. Otherwise, if value has a [[RegExpMatcher]] internal slot, then set serialized to
|
|
// { [[Type]]: "RegExp", [[RegExpMatcher]]: value.[[RegExpMatcher]], [[OriginalSource]]: value.[[OriginalSource]],
|
|
// [[OriginalFlags]]: value.[[OriginalFlags]] }.
|
|
else if (auto const* reg_exp_object = as_if<JS::RegExpObject>(object)) {
|
|
// NOTE: A Regex<ECMA262> object is perfectly happy to be reconstructed with just the source+flags.
|
|
// In the future, we could optimize the work being done on the deserialize step by serializing
|
|
// more of the internal state (the [[RegExpMatcher]] internal slot).
|
|
serialized.encode(ValueTag::RegExpObject);
|
|
serialized.encode(reg_exp_object->pattern());
|
|
serialized.encode(reg_exp_object->flags());
|
|
}
|
|
|
|
// 13. Otherwise, if value has an [[ArrayBufferData]] internal slot, then:
|
|
else if (auto const* array_buffer = as_if<JS::ArrayBuffer>(object)) {
|
|
TRY(serialize_array_buffer(m_vm, serialized, *array_buffer, m_for_storage));
|
|
}
|
|
|
|
// 14. Otherwise, if value has a [[ViewedArrayBuffer]] internal slot, then:
|
|
else if (auto const* typed_array_base = as_if<JS::TypedArrayBase>(object)) {
|
|
TRY(serialize_viewed_array_buffer(m_vm, serialized, *typed_array_base, m_for_storage, m_memory));
|
|
} else if (auto const* data_view = as_if<JS::DataView>(object)) {
|
|
TRY(serialize_viewed_array_buffer(m_vm, serialized, *data_view, m_for_storage, m_memory));
|
|
}
|
|
|
|
// 15. Otherwise, if value has a [[MapData]] internal slot, then:
|
|
else if (is<JS::Map>(object)) {
|
|
// 1. Set serialized to { [[Type]]: "Map", [[MapData]]: a new empty List }.
|
|
serialized.encode(ValueTag::MapObject);
|
|
|
|
// 2. Set deep to true.
|
|
deep = true;
|
|
}
|
|
|
|
// 16. Otherwise, if value has a [[SetData]] internal slot, then:
|
|
else if (is<JS::Set>(object)) {
|
|
// 1. Set serialized to { [[Type]]: "Set", [[SetData]]: a new empty List }.
|
|
serialized.encode(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 (is<JS::Error>(object) && !is<Bindings::PlatformObject>(object)) {
|
|
// 1. Let name be ? Get(value, "name").
|
|
auto name_property = TRY(object.get(m_vm.names.name));
|
|
|
|
// FIXME: Spec bug - https://github.com/whatwg/html/issues/9923
|
|
// MISSING STEP: Set name to ? ToString(name).
|
|
auto name = TRY(name_property.to_string(m_vm));
|
|
|
|
// 2. If name is not one of "Error", "EvalError", "RangeError", "ReferenceError", "SyntaxError", "TypeError", or "URIError", then set name to "Error".
|
|
auto type = error_name_to_type(name);
|
|
|
|
// 3. Let valueMessageDesc be ? value.[[GetOwnProperty]]("message").
|
|
auto value_message_descriptor = TRY(object.internal_get_own_property(m_vm.names.message));
|
|
|
|
// 4. Let message be undefined if IsDataDescriptor(valueMessageDesc) is false, and ? ToString(valueMessageDesc.[[Value]]) otherwise.
|
|
Optional<String> message;
|
|
if (value_message_descriptor.has_value() && value_message_descriptor->is_data_descriptor())
|
|
message = TRY(value_message_descriptor->value->to_string(m_vm));
|
|
|
|
// 5. Set serialized to { [[Type]]: "Error", [[Name]]: name, [[Message]]: message }.
|
|
// FIXME: 6. User agents should attach a serialized representation of any interesting accompanying data which are not yet specified, notably the stack property, to serialized.
|
|
serialized.encode(ValueTag::ErrorObject);
|
|
serialized.encode(type);
|
|
serialized.encode(message);
|
|
}
|
|
|
|
// 18. Otherwise, if value is an Array exotic object, then:
|
|
else if (is<JS::Array>(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, object));
|
|
|
|
// 3. Set serialized to { [[Type]]: "Array", [[Length]]: valueLen, [[Properties]]: a new empty List }.
|
|
serialized.encode(ValueTag::ArrayObject);
|
|
serialized.encode(length);
|
|
|
|
// 4. Set deep to true.
|
|
deep = true;
|
|
}
|
|
|
|
// 19. Otherwise, if value is a platform object that is a serializable object:
|
|
else if (auto const* serializable = as_if<Bindings::Serializable>(object)) {
|
|
// FIXME: 1. If value has a [[Detached]] internal slot whose value is true, then throw a "DataCloneError" DOMException.
|
|
|
|
// 2. Let typeString be the identifier of the primary interface of value.
|
|
// 3. Set serialized to { [[Type]]: typeString }.
|
|
serialized.encode(ValueTag::SerializableObject);
|
|
serialized.encode(serializable->serialize_type());
|
|
|
|
// 4. Set deep to true
|
|
deep = true;
|
|
}
|
|
|
|
// 20. Otherwise, if value is a platform object, then throw a "DataCloneError" DOMException.
|
|
else if (is<Bindings::PlatformObject>(object)) {
|
|
return throw_completion(WebIDL::DataCloneError::create(*m_vm.current_realm(), "Cannot serialize platform objects"_string));
|
|
}
|
|
|
|
// 21. Otherwise, if IsCallable(value) is true, then throw a "DataCloneError" DOMException.
|
|
else if (value.is_function()) {
|
|
return throw_completion(WebIDL::DataCloneError::create(*m_vm.current_realm(), "Cannot serialize functions"_string));
|
|
}
|
|
|
|
// FIXME: 22. Otherwise, if value has any internal slot other than [[Prototype]] or [[Extensible]], then throw a "DataCloneError" DOMException.
|
|
|
|
// 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.
|
|
|
|
// 24. Otherwise:
|
|
else {
|
|
// 1. Set serialized to { [[Type]]: "Object", [[Properties]]: a new empty List }.
|
|
serialized.encode(ValueTag::Object);
|
|
|
|
// 2. Set deep to true.
|
|
deep = true;
|
|
}
|
|
}
|
|
|
|
// 25. Set memory[value] to serialized.
|
|
m_memory.set(make_root(value), m_next_id++);
|
|
|
|
// 26. If deep is true, then:
|
|
if (deep) {
|
|
auto& object = value.as_object();
|
|
|
|
// 1. If value has a [[MapData]] internal slot, then:
|
|
if (auto const* map = as_if<JS::Map>(object)) {
|
|
// 1. Let copiedList be a new empty List.
|
|
Vector<JS::Value> copied_list;
|
|
copied_list.ensure_capacity(map->map_size() * 2);
|
|
|
|
// 2. For each Record { [[Key]], [[Value]] } entry of value.[[MapData]]:
|
|
for (auto const& entry : *map) {
|
|
// 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);
|
|
}
|
|
|
|
serialized.encode(map->map_size());
|
|
|
|
// 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]].
|
|
serialized.append(move(serialized_value));
|
|
}
|
|
}
|
|
|
|
// 2. Otherwise, if value has a [[SetData]] internal slot, then:
|
|
else if (auto const* set = as_if<JS::Set>(object)) {
|
|
// 1. Let copiedList be a new empty List.
|
|
Vector<JS::Value> copied_list;
|
|
copied_list.ensure_capacity(set->set_size());
|
|
|
|
// 2. For each entry of value.[[SetData]]:
|
|
for (auto const& entry : *set) {
|
|
// 1. If entry is not the special value empty, append entry to copiedList.
|
|
copied_list.append(entry.key);
|
|
}
|
|
|
|
serialized.encode(set->set_size());
|
|
|
|
// 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]].
|
|
serialized.append(move(serialized_value));
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
else if (auto* serializable = as_if<Bindings::Serializable>(object)) {
|
|
TRY(serializable->serialization_steps(serialized, m_for_storage, m_memory));
|
|
}
|
|
|
|
// 4. Otherwise, for each key in ! EnumerableOwnProperties(value, key):
|
|
else {
|
|
u64 property_count = 0;
|
|
auto count_offset = serialized.buffer().data().size();
|
|
serialized.encode(property_count);
|
|
|
|
for (auto key : MUST(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(object.has_own_property(property_key))) {
|
|
// 1. Let inputValue be ? value.[[Get]](key, value).
|
|
auto input_value = TRY(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]].
|
|
serialized.encode(key.as_string().utf8_string());
|
|
serialized.append(move(output_value));
|
|
|
|
++property_count;
|
|
}
|
|
}
|
|
|
|
if (property_count) {
|
|
auto* data = const_cast<u8*>(serialized.buffer().data().data());
|
|
memcpy(data + count_offset, &property_count, sizeof(property_count));
|
|
}
|
|
}
|
|
}
|
|
|
|
// 27. Return serialized.
|
|
return serialized.take_buffer().take_data();
|
|
}
|
|
|
|
private:
|
|
JS::VM& m_vm;
|
|
SerializationMemory& m_memory; // JS value -> index
|
|
u32 m_next_id { 0 };
|
|
bool m_for_storage { false };
|
|
};
|
|
|
|
class Deserializer {
|
|
public:
|
|
Deserializer(JS::VM& vm, TransferDataDecoder& serialized, JS::Realm& target_realm, DeserializationMemory& memory)
|
|
: m_vm(vm)
|
|
, m_serialized(serialized)
|
|
, m_memory(memory)
|
|
{
|
|
VERIFY(vm.current_realm() == &target_realm);
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/structured-data.html#structureddeserialize
|
|
WebIDL::ExceptionOr<JS::Value> deserialize()
|
|
{
|
|
auto& realm = *m_vm.current_realm();
|
|
|
|
auto tag = m_serialized.decode<ValueTag>();
|
|
|
|
// 2. If memory[serialized] exists, then return memory[serialized].
|
|
if (tag == ValueTag::ObjectReference) {
|
|
auto index = m_serialized.decode<u32>();
|
|
if (index == NumericLimits<u32>::max())
|
|
return JS::Object::create(*m_vm.current_realm(), nullptr);
|
|
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;
|
|
|
|
auto decode_string = [&]() {
|
|
auto string = m_serialized.decode<String>();
|
|
return JS::PrimitiveString::create(m_vm, string);
|
|
};
|
|
|
|
auto decode_big_int = [&]() {
|
|
auto string = m_serialized.decode<String>();
|
|
return JS::BigInt::create(m_vm, MUST(::Crypto::SignedBigInteger::from_base(10, string)));
|
|
};
|
|
|
|
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 { m_serialized.decode<bool>() };
|
|
is_primitive = true;
|
|
break;
|
|
case ValueTag::NumberPrimitive:
|
|
value = JS::Value { m_serialized.decode<double>() };
|
|
is_primitive = true;
|
|
break;
|
|
case ValueTag::BigIntPrimitive:
|
|
value = decode_big_int();
|
|
is_primitive = true;
|
|
break;
|
|
case ValueTag::StringPrimitive:
|
|
value = decode_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 ValueTag::BooleanObject:
|
|
value = JS::BooleanObject::create(realm, m_serialized.decode<bool>());
|
|
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:
|
|
value = JS::NumberObject::create(realm, m_serialized.decode<double>());
|
|
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:
|
|
value = JS::BigIntObject::create(realm, decode_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:
|
|
value = JS::StringObject::create(realm, decode_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:
|
|
value = JS::Date::create(realm, m_serialized.decode<double>());
|
|
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 = decode_string();
|
|
auto flags = decode_string();
|
|
|
|
value = MUST(JS::regexp_create(m_vm, pattern, flags));
|
|
break;
|
|
}
|
|
|
|
// 12. Otherwise, if serialized.[[Type]] is "SharedArrayBuffer", then:
|
|
case ValueTag::SharedArrayBuffer: {
|
|
// FIXME: 1. If targetRealm's corresponding agent cluster is not serialized.[[AgentCluster]], then throw a "DataCloneError" DOMException.
|
|
|
|
// 2. Otherwise, set value to a new SharedArrayBuffer object in targetRealm whose [[ArrayBufferData]] internal slot value is serialized.[[ArrayBufferData]]
|
|
// and whose [[ArrayBufferByteLength]] internal slot value is serialized.[[ArrayBufferByteLength]].
|
|
auto buffer = TRY(m_serialized.decode_buffer(realm));
|
|
value = JS::ArrayBuffer::create(realm, move(buffer), JS::DataBlock::Shared::Yes);
|
|
break;
|
|
}
|
|
|
|
// 13. Otherwise, if serialized.[[Type]] is "GrowableSharedArrayBuffer", then:
|
|
case ValueTag::GrowableSharedArrayBuffer: {
|
|
// FIXME: 1. If targetRealm's corresponding agent cluster is not serialized.[[AgentCluster]], then throw a "DataCloneError" DOMException.
|
|
|
|
// 2. Otherwise, set value to a new SharedArrayBuffer object in targetRealm whose [[ArrayBufferData]] internal slot value is serialized.[[ArrayBufferData]],
|
|
// whose [[ArrayBufferByteLengthData]] internal slot value is serialized.[[ArrayBufferByteLengthData]],
|
|
// and whose [[ArrayBufferMaxByteLength]] internal slot value is serialized.[[ArrayBufferMaxByteLength]].
|
|
auto buffer = TRY(m_serialized.decode_buffer(realm));
|
|
auto max_byte_length = m_serialized.decode<size_t>();
|
|
|
|
auto data = JS::ArrayBuffer::create(realm, move(buffer), JS::DataBlock::Shared::Yes);
|
|
data->set_max_byte_length(max_byte_length);
|
|
|
|
value = data;
|
|
break;
|
|
}
|
|
|
|
// 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 buffer = TRY(m_serialized.decode_buffer(realm));
|
|
value = JS::ArrayBuffer::create(realm, move(buffer));
|
|
break;
|
|
}
|
|
|
|
// 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]].
|
|
case ValueTag::ResizeableArrayBuffer: {
|
|
auto buffer = TRY(m_serialized.decode_buffer(realm));
|
|
auto max_byte_length = m_serialized.decode<size_t>();
|
|
|
|
auto data = JS::ArrayBuffer::create(realm, move(buffer));
|
|
data->set_max_byte_length(max_byte_length);
|
|
|
|
value = data;
|
|
break;
|
|
}
|
|
|
|
// 16. Otherwise, if serialized.[[Type]] is "ArrayBufferView", then:
|
|
case ValueTag::ArrayBufferView: {
|
|
auto array_buffer_value = TRY(deserialize());
|
|
auto& array_buffer = as<JS::ArrayBuffer>(array_buffer_value.as_object());
|
|
|
|
auto constructor_name = m_serialized.decode<String>();
|
|
auto byte_length = m_serialized.decode<u32>();
|
|
auto byte_offset = m_serialized.decode<u32>();
|
|
|
|
if (constructor_name == "DataView"sv) {
|
|
value = JS::DataView::create(realm, &array_buffer, byte_length, byte_offset);
|
|
} else {
|
|
auto array_length = m_serialized.decode<u32>();
|
|
|
|
GC::Ptr<JS::TypedArrayBase> typed_array;
|
|
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, Type) \
|
|
if (constructor_name == #ClassName##sv) \
|
|
typed_array = JS::ClassName::create(realm, array_length, array_buffer);
|
|
JS_ENUMERATE_TYPED_ARRAYS
|
|
#undef __JS_ENUMERATE
|
|
#undef CREATE_TYPED_ARRAY
|
|
|
|
VERIFY(typed_array); // FIXME: Handle errors better here? Can a fuzzer put weird stuff in the buffer?
|
|
|
|
typed_array->set_byte_length(byte_length);
|
|
typed_array->set_byte_offset(byte_offset);
|
|
value = typed_array;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// 17. Otherwise, if serialized.[[Type]] is "Map", then:
|
|
case ValueTag::MapObject: {
|
|
// 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: {
|
|
// 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: {
|
|
// 1. Let outputProto be targetRealm.[[Intrinsics]].[[%Array.prototype%]].
|
|
// 2. Set value to ! ArrayCreate(serialized.[[Length]], outputProto).
|
|
value = MUST(JS::Array::create(realm, m_serialized.decode<u64>(), realm.intrinsics().array_prototype()));
|
|
|
|
// 3. Set deep to true.
|
|
deep = true;
|
|
break;
|
|
}
|
|
|
|
// 20. Otherwise, if serialized.[[Type]] is "Object", then:
|
|
case ValueTag::Object: {
|
|
// 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 type = m_serialized.decode<ErrorType>();
|
|
auto message = m_serialized.decode<Optional<String>>();
|
|
|
|
GC::Ptr<JS::Error> error;
|
|
|
|
switch (type) {
|
|
case ErrorType::Error:
|
|
error = JS::Error::create(realm);
|
|
break;
|
|
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
|
|
case ErrorType::ClassName: \
|
|
error = JS::ClassName::create(realm); \
|
|
break;
|
|
JS_ENUMERATE_NATIVE_ERRORS
|
|
#undef __JS_ENUMERATE
|
|
}
|
|
|
|
VERIFY(error);
|
|
|
|
if (message.has_value())
|
|
error->set_message(message.release_value());
|
|
|
|
value = error;
|
|
break;
|
|
}
|
|
|
|
// 22. Otherwise:
|
|
default:
|
|
VERIFY(tag == ValueTag::SerializableObject);
|
|
|
|
// 1. Let interfaceName be serialized.[[Type]].
|
|
auto interface_name = m_serialized.decode<SerializeType>();
|
|
|
|
// 2. If the interface identified by interfaceName is not exposed in targetRealm, then throw a "DataCloneError" DOMException.
|
|
if (!is_serializable_interface_exposed_on_target_realm(interface_name, realm))
|
|
return WebIDL::DataCloneError::create(realm, "Unsupported type"_string);
|
|
|
|
// 3. Set value to a new instance of the interface identified by interfaceName, created in targetRealm.
|
|
value = create_serialized_type(interface_name, realm);
|
|
|
|
// 4. Set deep to true.
|
|
deep = true;
|
|
}
|
|
|
|
// 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 = as<JS::Map>(value.as_object());
|
|
auto length = m_serialized.decode<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 = as<JS::Set>(value.as_object());
|
|
auto length = m_serialized.decode<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 = m_serialized.decode<u64>();
|
|
|
|
// 1. For each Record { [[Key]], [[Value]] } entry of serialized.[[Properties]]:
|
|
for (u64 i = 0u; i < length; ++i) {
|
|
auto key = m_serialized.decode<String>();
|
|
|
|
// 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, deserialized_value));
|
|
|
|
// 3. Assert: result is true.
|
|
VERIFY(result);
|
|
}
|
|
}
|
|
|
|
// 4. Otherwise:
|
|
else {
|
|
// 1. Perform the appropriate deserialization steps for the interface identified by serialized.[[Type]], given serialized, value, and targetRealm.
|
|
auto& serializable = dynamic_cast<Bindings::Serializable&>(value.as_object());
|
|
TRY(serializable.deserialization_steps(m_serialized, m_memory));
|
|
}
|
|
}
|
|
|
|
// 25. Return value.
|
|
return value;
|
|
}
|
|
|
|
private:
|
|
static bool is_serializable_interface_exposed_on_target_realm(SerializeType name, JS::Realm& realm)
|
|
{
|
|
auto const& intrinsics = Bindings::host_defined_intrinsics(realm);
|
|
switch (name) {
|
|
case SerializeType::Blob:
|
|
return intrinsics.is_interface_exposed<Bindings::BlobPrototype>(realm);
|
|
case SerializeType::File:
|
|
return intrinsics.is_interface_exposed<Bindings::FilePrototype>(realm);
|
|
case SerializeType::FileList:
|
|
return intrinsics.is_interface_exposed<Bindings::FileListPrototype>(realm);
|
|
case SerializeType::DOMException:
|
|
return intrinsics.is_interface_exposed<Bindings::DOMExceptionPrototype>(realm);
|
|
case SerializeType::DOMMatrixReadOnly:
|
|
return intrinsics.is_interface_exposed<Bindings::DOMMatrixReadOnlyPrototype>(realm);
|
|
case SerializeType::DOMMatrix:
|
|
return intrinsics.is_interface_exposed<Bindings::DOMMatrixPrototype>(realm);
|
|
case SerializeType::DOMPointReadOnly:
|
|
return intrinsics.is_interface_exposed<Bindings::DOMPointReadOnlyPrototype>(realm);
|
|
case SerializeType::DOMPoint:
|
|
return intrinsics.is_interface_exposed<Bindings::DOMPointPrototype>(realm);
|
|
case SerializeType::DOMRectReadOnly:
|
|
return intrinsics.is_interface_exposed<Bindings::DOMRectReadOnlyPrototype>(realm);
|
|
case SerializeType::DOMRect:
|
|
return intrinsics.is_interface_exposed<Bindings::DOMRectPrototype>(realm);
|
|
case SerializeType::CryptoKey:
|
|
return intrinsics.is_interface_exposed<Bindings::CryptoKeyPrototype>(realm);
|
|
case SerializeType::DOMQuad:
|
|
return intrinsics.is_interface_exposed<Bindings::DOMQuadPrototype>(realm);
|
|
case SerializeType::ImageData:
|
|
return intrinsics.is_interface_exposed<Bindings::ImageDataPrototype>(realm);
|
|
case SerializeType::ImageBitmap:
|
|
return intrinsics.is_interface_exposed<Bindings::ImageBitmapPrototype>(realm);
|
|
case SerializeType::Unknown:
|
|
dbgln("Unknown interface type for serialization: {}", to_underlying(name));
|
|
break;
|
|
default:
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static GC::Ref<Bindings::PlatformObject> create_serialized_type(SerializeType serialize_type, JS::Realm& realm)
|
|
{
|
|
switch (serialize_type) {
|
|
case SerializeType::Blob:
|
|
return FileAPI::Blob::create(realm);
|
|
case SerializeType::File:
|
|
return FileAPI::File::create(realm);
|
|
case SerializeType::FileList:
|
|
return FileAPI::FileList::create(realm);
|
|
case SerializeType::DOMException:
|
|
return WebIDL::DOMException::create(realm);
|
|
case SerializeType::DOMMatrixReadOnly:
|
|
return Geometry::DOMMatrixReadOnly::create(realm);
|
|
case SerializeType::DOMMatrix:
|
|
return Geometry::DOMMatrix::create(realm);
|
|
case SerializeType::DOMPointReadOnly:
|
|
return Geometry::DOMPointReadOnly::create(realm);
|
|
case SerializeType::DOMPoint:
|
|
return Geometry::DOMPoint::create(realm);
|
|
case SerializeType::DOMRectReadOnly:
|
|
return Geometry::DOMRectReadOnly::create(realm);
|
|
case SerializeType::DOMRect:
|
|
return Geometry::DOMRect::create(realm);
|
|
case SerializeType::CryptoKey:
|
|
return Crypto::CryptoKey::create(realm);
|
|
case SerializeType::DOMQuad:
|
|
return Geometry::DOMQuad::create(realm);
|
|
case SerializeType::ImageData:
|
|
return ImageData::create(realm);
|
|
case SerializeType::ImageBitmap:
|
|
return ImageBitmap::create(realm);
|
|
case SerializeType::Unknown:
|
|
default:
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
}
|
|
|
|
JS::VM& m_vm;
|
|
TransferDataDecoder& m_serialized;
|
|
GC::RootVector<JS::Value> m_memory;
|
|
};
|
|
|
|
// https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializewithtransfer
|
|
WebIDL::ExceptionOr<SerializedTransferRecord> structured_serialize_with_transfer(JS::VM& vm, JS::Value value, Vector<GC::Root<JS::Object>> const& transfer_list)
|
|
{
|
|
// 1. Let memory be an empty map.
|
|
SerializationMemory memory = {};
|
|
|
|
// 2. For each transferable of transferList:
|
|
for (auto const& transferable : transfer_list) {
|
|
auto const* as_array_buffer = as_if<JS::ArrayBuffer>(*transferable);
|
|
|
|
// 1. If transferable has neither an [[ArrayBufferData]] internal slot nor a [[Detached]] internal slot, then throw a "DataCloneError" DOMException.
|
|
// FIXME: Handle transferring objects with [[Detached]] internal slot.
|
|
if (!as_array_buffer && !is<Bindings::Transferable>(*transferable))
|
|
return WebIDL::DataCloneError::create(*vm.current_realm(), "Cannot transfer type"_string);
|
|
|
|
// 2. If transferable has an [[ArrayBufferData]] internal slot and IsSharedArrayBuffer(transferable) is true, then throw a "DataCloneError" DOMException.
|
|
if (as_array_buffer && as_array_buffer->is_shared_array_buffer())
|
|
return WebIDL::DataCloneError::create(*vm.current_realm(), "Cannot transfer shared array buffer"_string);
|
|
|
|
JS::Value transferable_value { transferable };
|
|
|
|
// 3. If memory[transferable] exists, then throw a "DataCloneError" DOMException.
|
|
if (memory.contains(transferable_value))
|
|
return WebIDL::DataCloneError::create(*vm.current_realm(), "Cannot transfer value twice"_string);
|
|
|
|
// 4. Set memory[transferable] to { [[Type]]: an uninitialized value }.
|
|
memory.set(GC::make_root(transferable_value), memory.size());
|
|
}
|
|
|
|
// 3. Let serialized be ? StructuredSerializeInternal(value, false, memory).
|
|
auto serialized = TRY(structured_serialize_internal(vm, value, false, memory));
|
|
|
|
// 4. Let transferDataHolders be a new empty List.
|
|
Vector<TransferDataEncoder> transfer_data_holders;
|
|
transfer_data_holders.ensure_capacity(transfer_list.size());
|
|
|
|
// 5. For each transferable of transferList:
|
|
for (auto const& transferable : transfer_list) {
|
|
auto* array_buffer = as_if<JS::ArrayBuffer>(*transferable);
|
|
auto is_detached = array_buffer && array_buffer->is_detached();
|
|
|
|
// 1. If transferable has an [[ArrayBufferData]] internal slot and IsDetachedBuffer(transferable) is true, then throw a "DataCloneError" DOMException.
|
|
if (is_detached)
|
|
return WebIDL::DataCloneError::create(*vm.current_realm(), "Cannot transfer detached buffer"_string);
|
|
|
|
// 2. If transferable has a [[Detached]] internal slot and transferable.[[Detached]] is true, then throw a "DataCloneError" DOMException.
|
|
if (auto* transferable_object = as_if<Bindings::Transferable>(*transferable)) {
|
|
if (transferable_object->is_detached())
|
|
return WebIDL::DataCloneError::create(*vm.current_realm(), "Value already transferred"_string);
|
|
}
|
|
|
|
// 3. Let dataHolder be memory[transferable].
|
|
// IMPLEMENTATION DEFINED: We just create a data holder here, our memory holds indices into the SerializationRecord
|
|
TransferDataEncoder data_holder;
|
|
|
|
// 4. If transferable has an [[ArrayBufferData]] internal slot, then:
|
|
if (array_buffer) {
|
|
// 1. If transferable has an [[ArrayBufferMaxByteLength]] internal slot, then:
|
|
if (!array_buffer->is_fixed_length()) {
|
|
// 1. Set dataHolder.[[Type]] to "ResizableArrayBuffer".
|
|
data_holder.encode(TransferType::ResizableArrayBuffer);
|
|
|
|
// 2. Set dataHolder.[[ArrayBufferData]] to transferable.[[ArrayBufferData]].
|
|
// 3. Set dataHolder.[[ArrayBufferByteLength]] to transferable.[[ArrayBufferByteLength]].
|
|
data_holder.encode(array_buffer->buffer());
|
|
|
|
// 4. Set dataHolder.[[ArrayBufferMaxByteLength]] to transferable.[[ArrayBufferMaxByteLength]].
|
|
data_holder.encode(array_buffer->max_byte_length());
|
|
}
|
|
// 2. Otherwise:
|
|
else {
|
|
// 1. Set dataHolder.[[Type]] to "ArrayBuffer".
|
|
data_holder.encode(TransferType::ArrayBuffer);
|
|
|
|
// 2. Set dataHolder.[[ArrayBufferData]] to transferable.[[ArrayBufferData]].
|
|
// 3. Set dataHolder.[[ArrayBufferByteLength]] to transferable.[[ArrayBufferByteLength]].
|
|
data_holder.encode(array_buffer->buffer());
|
|
}
|
|
|
|
// 3. Perform ? DetachArrayBuffer(transferable).
|
|
// NOTE: Specifications can use the [[ArrayBufferDetachKey]] internal slot to prevent ArrayBuffers from being detached. This is used in WebAssembly JavaScript Interface, for example. See: https://html.spec.whatwg.org/multipage/references.html#refsWASMJS
|
|
TRY(JS::detach_array_buffer(vm, *array_buffer));
|
|
}
|
|
// 5. Otherwise:
|
|
else {
|
|
// 1. Assert: transferable is a platform object that is a transferable object.
|
|
auto& transferable_object = dynamic_cast<Bindings::Transferable&>(*transferable);
|
|
VERIFY(is<Bindings::PlatformObject>(*transferable));
|
|
|
|
// 2. Let interfaceName be the identifier of the primary interface of transferable.
|
|
auto interface_name = transferable_object.primary_interface();
|
|
|
|
// 3. Set dataHolder.[[Type]] to interfaceName.
|
|
data_holder.encode(interface_name);
|
|
|
|
// 4. Perform the appropriate transfer steps for the interface identified by interfaceName, given transferable and dataHolder.
|
|
TRY(transferable_object.transfer_steps(data_holder));
|
|
|
|
// 5. Set transferable.[[Detached]] to true.
|
|
transferable_object.set_detached(true);
|
|
}
|
|
|
|
// 6. Append dataHolder to transferDataHolders.
|
|
transfer_data_holders.append(move(data_holder));
|
|
}
|
|
|
|
// 6. Return { [[Serialized]]: serialized, [[TransferDataHolders]]: transferDataHolders }.
|
|
return SerializedTransferRecord { .serialized = move(serialized), .transfer_data_holders = move(transfer_data_holders) };
|
|
}
|
|
|
|
static bool is_transferable_interface_exposed_on_target_realm(TransferType name, JS::Realm& realm)
|
|
{
|
|
auto const& intrinsics = Bindings::host_defined_intrinsics(realm);
|
|
switch (name) {
|
|
case TransferType::MessagePort:
|
|
return intrinsics.is_interface_exposed<Bindings::MessagePortPrototype>(realm);
|
|
case TransferType::ReadableStream:
|
|
return intrinsics.is_interface_exposed<Bindings::ReadableStreamPrototype>(realm);
|
|
case TransferType::WritableStream:
|
|
return intrinsics.is_interface_exposed<Bindings::WritableStreamPrototype>(realm);
|
|
case TransferType::TransformStream:
|
|
return intrinsics.is_interface_exposed<Bindings::TransformStreamPrototype>(realm);
|
|
case TransferType::Unknown:
|
|
dbgln("Unknown interface type for transfer: {}", to_underlying(name));
|
|
break;
|
|
default:
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static WebIDL::ExceptionOr<GC::Ref<Bindings::PlatformObject>> create_transferred_value(TransferType name, JS::Realm& target_realm, TransferDataDecoder& decoder)
|
|
{
|
|
switch (name) {
|
|
case TransferType::MessagePort: {
|
|
auto message_port = HTML::MessagePort::create(target_realm);
|
|
TRY(message_port->transfer_receiving_steps(decoder));
|
|
return message_port;
|
|
}
|
|
case TransferType::ReadableStream: {
|
|
auto readable_stream = target_realm.create<Streams::ReadableStream>(target_realm);
|
|
TRY(readable_stream->transfer_receiving_steps(decoder));
|
|
return readable_stream;
|
|
}
|
|
case TransferType::WritableStream: {
|
|
auto writable_stream = target_realm.create<Streams::WritableStream>(target_realm);
|
|
TRY(writable_stream->transfer_receiving_steps(decoder));
|
|
return writable_stream;
|
|
}
|
|
case TransferType::TransformStream: {
|
|
auto transform_stream = target_realm.create<Streams::TransformStream>(target_realm);
|
|
TRY(transform_stream->transfer_receiving_steps(decoder));
|
|
return transform_stream;
|
|
}
|
|
case TransferType::ArrayBuffer:
|
|
case TransferType::ResizableArrayBuffer:
|
|
case TransferType::Unknown:
|
|
break;
|
|
}
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/structured-data.html#structureddeserializewithtransfer
|
|
WebIDL::ExceptionOr<DeserializedTransferRecord> structured_deserialize_with_transfer(SerializedTransferRecord& serialize_with_transfer_result, JS::Realm& target_realm)
|
|
{
|
|
auto& vm = target_realm.vm();
|
|
|
|
// 1. Let memory be an empty map.
|
|
auto memory = DeserializationMemory(vm.heap());
|
|
|
|
// 2. Let transferredValues be a new empty List.
|
|
Vector<GC::Root<JS::Object>> transferred_values;
|
|
|
|
// 3. For each transferDataHolder of serializeWithTransferResult.[[TransferDataHolders]]:
|
|
for (auto& transfer_data_holder : serialize_with_transfer_result.transfer_data_holders) {
|
|
if (transfer_data_holder.buffer().data().is_empty())
|
|
continue;
|
|
|
|
TransferDataDecoder decoder { move(transfer_data_holder) };
|
|
|
|
// 1. Let value be an uninitialized value.
|
|
auto value = TRY(structured_deserialize_with_transfer_internal(decoder, target_realm));
|
|
|
|
// 5. Set memory[transferDataHolder] to value.
|
|
memory.append(value);
|
|
|
|
// 6. Append value to transferredValues.
|
|
transferred_values.append(GC::make_root(value.as_object()));
|
|
}
|
|
|
|
// 4. Let deserialized be ? StructuredDeserialize(serializeWithTransferResult.[[Serialized]], targetRealm, memory).
|
|
auto deserialized = TRY(structured_deserialize(vm, serialize_with_transfer_result.serialized, target_realm, memory));
|
|
|
|
// 5. Return { [[Deserialized]]: deserialized, [[TransferredValues]]: transferredValues }.
|
|
return DeserializedTransferRecord { .deserialized = deserialized, .transferred_values = move(transferred_values) };
|
|
}
|
|
|
|
// AD-HOC: This non-standard overload is meant to extract just one transferrable value from a serialized transfer record.
|
|
// It's primarily useful for an object's transfer receiving steps to deserialize a nested value.
|
|
WebIDL::ExceptionOr<JS::Value> structured_deserialize_with_transfer_internal(TransferDataDecoder& decoder, JS::Realm& target_realm)
|
|
{
|
|
auto type = decoder.decode<TransferType>();
|
|
|
|
// 1. Let value be an uninitialized value.
|
|
JS::Value value;
|
|
|
|
// 2. If transferDataHolder.[[Type]] is "ArrayBuffer", then set value to a new ArrayBuffer object in targetRealm
|
|
// whose [[ArrayBufferData]] internal slot value is transferDataHolder.[[ArrayBufferData]], and
|
|
// whose [[ArrayBufferByteLength]] internal slot value is transferDataHolder.[[ArrayBufferByteLength]].
|
|
// NOTE: In cases where the original memory occupied by [[ArrayBufferData]] is accessible during the deserialization,
|
|
// this step is unlikely to throw an exception, as no new memory needs to be allocated: the memory occupied by
|
|
// [[ArrayBufferData]] is instead just getting transferred into the new ArrayBuffer. This could be true, for example,
|
|
// when both the source and target realms are in the same process.
|
|
if (type == TransferType::ArrayBuffer) {
|
|
auto buffer = TRY(decoder.decode_buffer(target_realm));
|
|
value = JS::ArrayBuffer::create(target_realm, move(buffer));
|
|
}
|
|
|
|
// 3. Otherwise, if transferDataHolder.[[Type]] is "ResizableArrayBuffer", then set value to a new ArrayBuffer object
|
|
// in targetRealm whose [[ArrayBufferData]] internal slot value is transferDataHolder.[[ArrayBufferData]], whose
|
|
// [[ArrayBufferByteLength]] internal slot value is transferDataHolder.[[ArrayBufferByteLength]], and whose
|
|
// [[ArrayBufferMaxByteLength]] internal slot value is transferDataHolder.[[ArrayBufferMaxByteLength]].
|
|
// NOTE: For the same reason as the previous step, this step is also unlikely to throw an exception.
|
|
else if (type == TransferType::ResizableArrayBuffer) {
|
|
auto buffer = TRY(decoder.decode_buffer(target_realm));
|
|
auto max_byte_length = decoder.decode<size_t>();
|
|
|
|
auto data = JS::ArrayBuffer::create(target_realm, move(buffer));
|
|
data->set_max_byte_length(max_byte_length);
|
|
|
|
value = data;
|
|
}
|
|
|
|
// 4. Otherwise:
|
|
else {
|
|
// 1. Let interfaceName be transferDataHolder.[[Type]].
|
|
// 2. If the interface identified by interfaceName is not exposed in targetRealm, then throw a "DataCloneError" DOMException.
|
|
if (!is_transferable_interface_exposed_on_target_realm(type, target_realm))
|
|
return WebIDL::DataCloneError::create(target_realm, "Unknown type transferred"_string);
|
|
|
|
// 3. Set value to a new instance of the interface identified by interfaceName, created in targetRealm.
|
|
// 4. Perform the appropriate transfer-receiving steps for the interface identified by interfaceName given transferDataHolder and value.
|
|
value = TRY(create_transferred_value(type, target_realm, decoder));
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/structured-data.html#structuredserialize
|
|
WebIDL::ExceptionOr<SerializationRecord> structured_serialize(JS::VM& vm, JS::Value value)
|
|
{
|
|
// 1. Return ? StructuredSerializeInternal(value, false).
|
|
SerializationMemory memory = {};
|
|
return structured_serialize_internal(vm, value, false, memory);
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializeforstorage
|
|
WebIDL::ExceptionOr<SerializationRecord> structured_serialize_for_storage(JS::VM& vm, JS::Value value)
|
|
{
|
|
// 1. Return ? StructuredSerializeInternal(value, true).
|
|
SerializationMemory memory = {};
|
|
return structured_serialize_internal(vm, value, true, memory);
|
|
}
|
|
|
|
// 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, SerializationMemory& memory)
|
|
{
|
|
// 1. If memory was not supplied, let memory be an empty map.
|
|
// IMPLEMENTATION DEFINED: We move this requirement up to the callers to make recursion easier
|
|
|
|
Serializer serializer(vm, memory, for_storage);
|
|
return serializer.serialize(value);
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/structured-data.html#structureddeserialize
|
|
WebIDL::ExceptionOr<JS::Value> structured_deserialize(JS::VM& vm, SerializationRecord const& serialized, JS::Realm& target_realm, Optional<DeserializationMemory> memory)
|
|
{
|
|
TemporaryExecutionContext execution_context { target_realm };
|
|
|
|
if (!memory.has_value())
|
|
memory = DeserializationMemory { vm.heap() };
|
|
|
|
TransferDataDecoder decoder { serialized };
|
|
return structured_deserialize_internal(vm, decoder, target_realm, *memory);
|
|
}
|
|
|
|
WebIDL::ExceptionOr<JS::Value> structured_deserialize_internal(JS::VM& vm, TransferDataDecoder& serialized, JS::Realm& target_realm, DeserializationMemory& memory)
|
|
{
|
|
Deserializer deserializer(vm, serialized, target_realm, memory);
|
|
return deserializer.deserialize();
|
|
}
|
|
|
|
TransferDataEncoder::TransferDataEncoder()
|
|
: m_encoder(m_buffer)
|
|
{
|
|
}
|
|
|
|
TransferDataEncoder::TransferDataEncoder(IPC::MessageBuffer&& buffer)
|
|
: m_buffer(move(buffer))
|
|
, m_encoder(m_buffer)
|
|
{
|
|
}
|
|
|
|
void TransferDataEncoder::append(SerializationRecord&& record)
|
|
{
|
|
MUST(m_buffer.append_data(record.data(), record.size()));
|
|
}
|
|
|
|
void TransferDataEncoder::extend(Vector<TransferDataEncoder> data_holders)
|
|
{
|
|
for (auto& data_holder : data_holders)
|
|
MUST(m_buffer.extend(move(data_holder.m_buffer)));
|
|
}
|
|
|
|
TransferDataDecoder::TransferDataDecoder(SerializationRecord const& record)
|
|
: m_stream(record.span())
|
|
, m_decoder(m_stream, m_files)
|
|
{
|
|
}
|
|
|
|
TransferDataDecoder::TransferDataDecoder(TransferDataEncoder&& data_holder)
|
|
: m_buffer(data_holder.take_buffer())
|
|
, m_stream(m_buffer.data().span())
|
|
, m_decoder(m_stream, m_files)
|
|
{
|
|
// FIXME: The churn between IPC::File and IPC::AutoCloseFileDescriptor is pretty awkward, we should find a way to
|
|
// consolidate the way we use these type.
|
|
for (auto& auto_fd : m_buffer.take_fds())
|
|
m_files.enqueue(IPC::File::adopt_fd(auto_fd->take_fd()));
|
|
}
|
|
|
|
WebIDL::ExceptionOr<ByteBuffer> TransferDataDecoder::decode_buffer(JS::Realm& realm)
|
|
{
|
|
auto buffer = m_decoder.decode<ByteBuffer>();
|
|
|
|
if (buffer.is_error()) {
|
|
VERIFY(buffer.error().code() == ENOMEM);
|
|
return WebIDL::DataCloneError::create(realm, "Unable to allocate memory for transferred buffer"_string);
|
|
}
|
|
|
|
return buffer.release_value();
|
|
}
|
|
|
|
}
|
|
|
|
namespace IPC {
|
|
|
|
template<>
|
|
ErrorOr<void> encode(Encoder& encoder, Web::HTML::TransferDataEncoder const& data_holder)
|
|
{
|
|
// FIXME: The churn between IPC::File and IPC::AutoCloseFileDescriptor is pretty awkward, we should find a way to
|
|
// consolidate the way we use these type.
|
|
Vector<IPC::File> files;
|
|
files.ensure_capacity(data_holder.buffer().fds().size());
|
|
|
|
for (auto const& auto_fd : data_holder.buffer().fds()) {
|
|
auto fd = const_cast<AutoCloseFileDescriptor&>(*auto_fd).take_fd();
|
|
files.unchecked_append(IPC::File::adopt_fd(fd));
|
|
}
|
|
|
|
TRY(encoder.encode(data_holder.buffer().data()));
|
|
TRY(encoder.encode(files));
|
|
return {};
|
|
}
|
|
|
|
template<>
|
|
ErrorOr<Web::HTML::TransferDataEncoder> decode(Decoder& decoder)
|
|
{
|
|
auto data = TRY(decoder.decode<Web::HTML::SerializationRecord>());
|
|
auto files = TRY(decoder.decode<Vector<IPC::File>>());
|
|
|
|
// FIXME: The churn between IPC::File and IPC::AutoCloseFileDescriptor is pretty awkward, we should find a way to
|
|
// consolidate the way we use these type.
|
|
MessageFileType auto_files;
|
|
auto_files.ensure_capacity(files.size());
|
|
|
|
for (auto& fd : files) {
|
|
auto auto_fd = adopt_ref(*new AutoCloseFileDescriptor(fd.take_fd()));
|
|
auto_files.unchecked_append(move(auto_fd));
|
|
}
|
|
|
|
IPC::MessageBuffer buffer { move(data), move(auto_files) };
|
|
return Web::HTML::TransferDataEncoder { move(buffer) };
|
|
}
|
|
|
|
template<>
|
|
ErrorOr<void> encode(Encoder& encoder, Web::HTML::SerializedTransferRecord const& record)
|
|
{
|
|
TRY(encoder.encode(record.serialized));
|
|
TRY(encoder.encode(record.transfer_data_holders));
|
|
return {};
|
|
}
|
|
|
|
template<>
|
|
ErrorOr<Web::HTML::SerializedTransferRecord> decode(Decoder& decoder)
|
|
{
|
|
auto serialized = TRY(decoder.decode<Web::HTML::SerializationRecord>());
|
|
auto transfer_data_holders = TRY(decoder.decode<Vector<Web::HTML::TransferDataEncoder>>());
|
|
|
|
return Web::HTML::SerializedTransferRecord { move(serialized), move(transfer_data_holders) };
|
|
}
|
|
|
|
}
|