mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-27 11:19:36 +00:00
Our structured serialization implementation had its own bespoke encoder and decoder to serialize JS values. It also used a u32 buffer under the hood, which made using its structures a bit awkward. We had previously worked around its data structures in transferable streams, which nested transfers of MessagePort instances. We basically had to add hooks into the MessagePort to route to the correct transfer receiving steps, and we could not invoke the correct AOs directly as the spec dictates. We now use IPC mechanics to encode and decode data. This works because, although we are encoding JS values, we are only ultimately encoding primitive and basic AK types. The resulting data structures actually enforce that we implement transferable streams exactly as the spec is worded (I had planned to do that in a separate commit, but the fallout of this patch actually required that change).
1404 lines
66 KiB
C++
1404 lines
66 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;
|
|
}
|
|
|
|
static WebIDL::ExceptionOr<void> serialize_array_buffer(JS::VM&, TransferDataEncoder&, JS::ArrayBuffer const&, bool for_storage);
|
|
|
|
template<OneOf<JS::TypedArrayBase, JS::DataView> ViewType>
|
|
static WebIDL::ExceptionOr<void> serialize_viewed_array_buffer(JS::VM&, TransferDataEncoder&, ViewType const&, bool for_storage, SerializationMemory&);
|
|
|
|
// 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 };
|
|
};
|
|
|
|
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 {
|
|
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) };
|
|
}
|
|
|
|
}
|