ladybird/Libraries/LibWeb/HTML/StructuredSerialize.cpp
Timothy Flynn ed71db45e7 LibWeb: Move array buffer/view serializers above their users
Now that these serializers are internal to StructuredSerialize.cpp,
let's put them above Serializer so they don't have to be forward-
declared and explicitly instantiated.
2025-07-18 10:09:02 -04:00

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) };
}
}