/* * Copyright (c) 2022, Daniel Ehrenberg * Copyright (c) 2022, Andrew Kaster * Copyright (c) 2023, Kenneth Myhra * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Web::HTML { static WebIDL::ExceptionOr structured_deserialize_impl(JS::VM& vm, ReadonlySpan serialized, JS::Realm& target_realm, SerializationMemory& memory); // Binary format: // A list of adjacent shallow values, which may contain references to other // values (noted by their position in the list, one value following another). // This list represents the "memory" in the StructuredSerialize algorithm. // The first item in the list is the root, i.e., the value of everything. // The format is generally u32-aligned (hence this leaking out into the type) // Each value has a length based on its type, as defined below. // // (Should more redundancy be added, e.g., for lengths/positions of values?) enum ValueTag { // Unused, for ease of catching bugs. Empty, // UndefinedPrimitive is serialized indicating that the Type is Undefined, no value is serialized. UndefinedPrimitive, // NullPrimitive is serialized indicating that the Type is Null, no value is serialized. NullPrimitive, // Following u32 is the boolean value. BooleanPrimitive, // Following two u32s are the double value. NumberPrimitive, // The BigIntPrimitive is serialized as a string in base 10 representation. // Following two u32s representing the length of the string, then the following u32s, equal to size, is the string representation. BigIntPrimitive, // Following two u32s representing the length of the string, then the following u32s, equal to size, is the string representation. StringPrimitive, BooleanObject, NumberObject, BigIntObject, StringObject, DateObject, RegExpObject, GrowableSharedArrayBuffer, SharedArrayBuffer, ResizeableArrayBuffer, ArrayBuffer, ArrayBufferView, ErrorObject, // TODO: Define many more types // This tag or higher are understood to be errors ValueTagMax, }; 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; } // 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_for_storage(for_storage) { } // https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializeinternal WebIDL::ExceptionOr serialize(JS::Value value) { // 2. If memory[value] exists, then return memory[value]. // FIXME: Do callers actually need a copy? or can they get away with a range? if (m_memory.contains(value)) { auto range = m_memory.get(value).value(); return m_serialized.span().slice(range.start, range.end); } // 3. Let deep be false. [[maybe_unused]] bool deep = false; bool return_primitive_type = true; // 4. If Type(value) is Undefined, Null, Boolean, Number, BigInt, or String, then return { [[Type]]: "primitive", [[Value]]: value }. if (value.is_undefined()) { m_serialized.append(ValueTag::UndefinedPrimitive); } else if (value.is_null()) { m_serialized.append(ValueTag::NullPrimitive); } else if (value.is_boolean()) { m_serialized.append(ValueTag::BooleanPrimitive); m_serialized.append(static_cast(value.as_bool())); } else if (value.is_number()) { m_serialized.append(ValueTag::NumberPrimitive); double number = value.as_double(); m_serialized.append(bit_cast(&number), 2); } else if (value.is_bigint()) { m_serialized.append(ValueTag::BigIntPrimitive); auto& val = value.as_bigint(); TRY(serialize_string(m_serialized, TRY_OR_THROW_OOM(m_vm, val.to_string()))); } else if (value.is_string()) { m_serialized.append(ValueTag::StringPrimitive); TRY(serialize_string(m_serialized, value.as_string())); } else { return_primitive_type = false; } if (return_primitive_type) return m_serialized; // 5. If Type(value) is Symbol, then throw a "DataCloneError" DOMException. if (value.is_symbol()) return WebIDL::DataCloneError::create(*m_vm.current_realm(), "Cannot serialize Symbol"_fly_string); // 6. Let serialized be an uninitialized value. // NOTE: We use the range of the soon-to-be-serialized value in our serialized data buffer // to be the `serialized` spec value. auto serialized_start = m_serialized.size(); // 7. If value has a [[BooleanData]] internal slot, then set serialized to { [[Type]]: "Boolean", [[BooleanData]]: value.[[BooleanData]] }. if (value.is_object() && is(value.as_object())) { m_serialized.append(ValueTag::BooleanObject); auto& boolean_object = static_cast(value.as_object()); m_serialized.append(bit_cast(static_cast(boolean_object.boolean()))); } // 8. Otherwise, if value has a [[NumberData]] internal slot, then set serialized to { [[Type]]: "Number", [[NumberData]]: value.[[NumberData]] }. else if (value.is_object() && is(value.as_object())) { m_serialized.append(ValueTag::NumberObject); auto& number_object = static_cast(value.as_object()); double const number = number_object.number(); m_serialized.append(bit_cast(&number), 2); } // 9. Otherwise, if value has a [[BigIntData]] internal slot, then set serialized to { [[Type]]: "BigInt", [[BigIntData]]: value.[[BigIntData]] }. else if (value.is_object() && is(value.as_object())) { m_serialized.append(ValueTag::BigIntObject); auto& bigint_object = static_cast(value.as_object()); TRY(serialize_string(m_serialized, TRY_OR_THROW_OOM(m_vm, bigint_object.bigint().to_string()))); } // 10. Otherwise, if value has a [[StringData]] internal slot, then set serialized to { [[Type]]: "String", [[StringData]]: value.[[StringData]] }. else if (value.is_object() && is(value.as_object())) { m_serialized.append(ValueTag::StringObject); auto& string_object = static_cast(value.as_object()); TRY(serialize_string(m_serialized, string_object.primitive_string())); } // 11. Otherwise, if value has a [[DateValue]] internal slot, then set serialized to { [[Type]]: "Date", [[DateValue]]: value.[[DateValue]] }. else if (value.is_object() && is(value.as_object())) { m_serialized.append(ValueTag::DateObject); auto& date_object = static_cast(value.as_object()); double const date_value = date_object.date_value(); m_serialized.append(bit_cast(&date_value), 2); } // 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 (value.is_object() && is(value.as_object())) { m_serialized.append(ValueTag::RegExpObject); auto& regexp_object = static_cast(value.as_object()); // Note: A Regex 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) TRY(serialize_string(m_serialized, TRY_OR_THROW_OOM(m_vm, String::from_deprecated_string(regexp_object.pattern())))); TRY(serialize_string(m_serialized, TRY_OR_THROW_OOM(m_vm, String::from_deprecated_string(regexp_object.flags())))); } // 13. Otherwise, if value has an [[ArrayBufferData]] internal slot, then: else if (value.is_object() && is(value.as_object())) { TRY(serialize_array_buffer(m_serialized, static_cast(value.as_object()))); } // 14. Otherwise, if value has a [[ViewedArrayBuffer]] internal slot, then: else if (value.is_object() && is(value.as_object())) { TRY(serialize_viewed_array_buffer(m_serialized, static_cast(value.as_object()))); } else if (value.is_object() && is(value.as_object())) { TRY(serialize_viewed_array_buffer(m_serialized, static_cast(value.as_object()))); } // 17. Otherwise, if value has an [[ErrorData]] internal slot and value is not a platform object, then: else if (value.is_object() && is(value.as_object()) && !is(value.as_object())) { // 1. Let name be ? Get(value, "name"). auto name_property = TRY(value.as_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(value.as_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 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. m_serialized.append(ValueTag::ErrorObject); m_serialized.append(type); m_serialized.append(message.has_value()); if (message.has_value()) TRY(serialize_string(m_serialized, *message)); } // 15, 16, 18 - 24: FIXME: Serialize other data types else { return throw_completion(WebIDL::DataCloneError::create(*m_vm.current_realm(), "Unsupported type"_fly_string)); } // 25. Set memory[value] to serialized. auto serialized_end = m_serialized.size(); m_memory.set(make_handle(value), { serialized_start, serialized_end }); // Second pass: Update the objects to point to other objects in memory return m_serialized; } private: JS::VM& m_vm; SerializationMemory& m_memory; // JS value -> index SerializationRecord m_serialized; bool m_for_storage { false }; WebIDL::ExceptionOr serialize_bytes(Vector& vector, ReadonlyBytes bytes) { // Append size of the buffer to the serialized structure. u64 const size = bytes.size(); TRY_OR_THROW_OOM(m_vm, vector.try_append(bit_cast(&size), 2)); // Append the bytes of the buffer to the serialized structure. u64 byte_position = 0; while (byte_position < size) { u32 combined_value = 0; for (u8 i = 0; i < 4; ++i) { u8 const byte = bytes[byte_position]; combined_value |= byte << (i * 8); byte_position++; if (byte_position == size) break; } TRY_OR_THROW_OOM(m_vm, vector.try_append(combined_value)); } return {}; } WebIDL::ExceptionOr serialize_string(Vector& vector, DeprecatedFlyString const& string) { return serialize_bytes(vector, string.view().bytes()); } WebIDL::ExceptionOr serialize_string(Vector& vector, String const& string) { return serialize_bytes(vector, { string.code_points().bytes(), string.code_points().byte_length() }); } WebIDL::ExceptionOr serialize_string(Vector& vector, JS::PrimitiveString const& primitive_string) { auto string = primitive_string.utf8_string(); TRY(serialize_string(vector, string)); return {}; } WebIDL::ExceptionOr serialize_array_buffer(Vector& vector, JS::ArrayBuffer const& array_buffer) { // 13. Otherwise, if value has an [[ArrayBufferData]] internal slot, then: // FIXME: 1. If IsSharedArrayBuffer(value) is true, then: if (false) { // 1. If the current settings object's cross-origin isolated capability is false, then throw a "DataCloneError" DOMException. // NOTE: This check is only needed when serializing (and not when deserializing) as the cross-origin isolated capability cannot change // over time and a SharedArrayBuffer cannot leave an agent cluster. if (current_settings_object().cross_origin_isolated_capability() == CanUseCrossOriginIsolatedAPIs::No) return WebIDL::DataCloneError::create(*m_vm.current_realm(), "Cannot serialize SharedArrayBuffer when cross-origin isolated"_fly_string); // 2. If forStorage is true, then throw a "DataCloneError" DOMException. if (m_for_storage) return WebIDL::DataCloneError::create(*m_vm.current_realm(), "Cannot serialize SharedArrayBuffer for storage"_fly_string); // FIXME: 3. If value has an [[ArrayBufferMaxByteLength]] internal slot, then set serialized to { [[Type]]: "GrowableSharedArrayBuffer", // [[ArrayBufferData]]: value.[[ArrayBufferData]], [[ArrayBufferByteLengthData]]: value.[[ArrayBufferByteLengthData]], // [[ArrayBufferMaxByteLength]]: value.[[ArrayBufferMaxByteLength]], [[AgentCluster]]: the surrounding agent's agent cluster }. // FIXME: 4. Otherwise, set serialized to { [[Type]]: "SharedArrayBuffer", [[ArrayBufferData]]: value.[[ArrayBufferData]], // [[ArrayBufferByteLength]]: value.[[ArrayBufferByteLength]], [[AgentCluster]]: the surrounding agent's agent cluster }. } // 2. Otherwise: else { // 1. If IsDetachedBuffer(value) is true, then throw a "DataCloneError" DOMException. if (array_buffer.is_detached()) return WebIDL::DataCloneError::create(*m_vm.current_realm(), "Cannot serialize detached ArrayBuffer"_fly_string); // 2. Let size be value.[[ArrayBufferByteLength]]. auto size = array_buffer.byte_length(); // 3. Let dataCopy be ? CreateByteDataBlock(size). // NOTE: This can throw a RangeError exception upon allocation failure. auto data_copy = TRY(JS::create_byte_data_block(m_vm, size)); // 4. Perform CopyDataBlockBytes(dataCopy, 0, value.[[ArrayBufferData]], 0, size). JS::copy_data_block_bytes(data_copy.buffer(), 0, array_buffer.buffer(), 0, size); // FIXME: 5. If value has an [[ArrayBufferMaxByteLength]] internal slot, then set serialized to { [[Type]]: "ResizableArrayBuffer", // [[ArrayBufferData]]: dataCopy, [[ArrayBufferByteLength]]: size, [[ArrayBufferMaxByteLength]]: value.[[ArrayBufferMaxByteLength]] }. if (false) { } // 6. Otherwise, set serialized to { [[Type]]: "ArrayBuffer", [[ArrayBufferData]]: dataCopy, [[ArrayBufferByteLength]]: size }. else { vector.append(ValueTag::ArrayBuffer); TRY(serialize_bytes(vector, data_copy.buffer().bytes())); } } return {}; } template ViewType> WebIDL::ExceptionOr serialize_viewed_array_buffer(Vector& vector, ViewType const& view) { // 14. Otherwise, if value has a [[ViewedArrayBuffer]] internal slot, then: // FIXME: 1. If IsArrayBufferViewOutOfBounds(value) is true, then throw a "DataCloneError" DOMException. // 2. Let buffer be the value of value's [[ViewedArrayBuffer]] internal slot. auto* buffer = view.viewed_array_buffer(); // 3. Let bufferSerialized be ? StructuredSerializeInternal(buffer, forStorage, memory). auto buffer_serialized = TRY(structured_serialize_internal(m_vm, JS::Value(buffer), m_for_storage, m_memory)); // 4. Assert: bufferSerialized.[[Type]] is "ArrayBuffer", "ResizableArrayBuffer", "SharedArrayBuffer", or "GrowableSharedArrayBuffer". // NOTE: We currently only implement this for ArrayBuffer VERIFY(buffer_serialized[0] == ValueTag::ArrayBuffer); // 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) { vector.append(ValueTag::ArrayBufferView); u64 const serialized_buffer_size = buffer_serialized.size(); vector.append(bit_cast(&serialized_buffer_size), 2); vector.extend(move(buffer_serialized)); // [[ArrayBufferSerialized]] TRY(serialize_string(vector, "DataView"_string)); // [[Constructor]] vector.append(view.byte_length()); vector.append(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]] }. vector.append(ValueTag::ArrayBufferView); u64 const serialized_buffer_size = buffer_serialized.size(); vector.append(bit_cast(&serialized_buffer_size), 2); vector.extend(move(buffer_serialized)); // [[ArrayBufferSerialized]] TRY(serialize_string(vector, view.element_name())); // [[Constructor]] vector.append(view.byte_length()); vector.append(view.byte_offset()); vector.append(view.array_length()); } return {}; } }; class Deserializer { public: Deserializer(JS::VM& vm, JS::Realm& target_realm, ReadonlySpan v, SerializationMemory& serialization_memory) : m_vm(vm) , m_vector(v) , m_memory(target_realm.heap()) , m_serialization_memory(serialization_memory) { VERIFY(vm.current_realm() == &target_realm); } WebIDL::ExceptionOr deserialize() { // First pass: fill up the memory with new values u32 position = 0; while (position < m_vector.size()) { switch (m_vector[position++]) { case ValueTag::UndefinedPrimitive: { m_memory.append(JS::js_undefined()); break; } case ValueTag::NullPrimitive: { m_memory.append(JS::js_null()); break; } case ValueTag::BooleanPrimitive: { m_memory.append(JS::Value(static_cast(m_vector[position++]))); break; } case ValueTag::NumberPrimitive: { u32 bits[2]; bits[0] = m_vector[position++]; bits[1] = m_vector[position++]; double value = *bit_cast(&bits); m_memory.append(JS::Value(value)); break; } case ValueTag::BigIntPrimitive: { auto big_int = TRY(deserialize_big_int_primitive(m_vm, m_vector, position)); m_memory.append(JS::Value { big_int }); break; } case ValueTag::StringPrimitive: { auto string = TRY(deserialize_string_primitive(m_vm, m_vector, position)); m_memory.append(JS::Value { string }); break; } case BooleanObject: { auto* realm = m_vm.current_realm(); bool const value = static_cast(m_vector[position++]); m_memory.append(JS::BooleanObject::create(*realm, value)); break; } case ValueTag::NumberObject: { auto* realm = m_vm.current_realm(); u32 bits[2]; bits[0] = m_vector[position++]; bits[1] = m_vector[position++]; double const value = *bit_cast(&bits); m_memory.append(JS::NumberObject::create(*realm, value)); break; } case ValueTag::BigIntObject: { auto* realm = m_vm.current_realm(); auto big_int = TRY(deserialize_big_int_primitive(m_vm, m_vector, position)); m_memory.append(JS::BigIntObject::create(*realm, big_int)); break; } case ValueTag::StringObject: { auto* realm = m_vm.current_realm(); auto string = TRY(deserialize_string_primitive(m_vm, m_vector, position)); m_memory.append(JS::StringObject::create(*realm, string, realm->intrinsics().string_prototype())); break; } case ValueTag::DateObject: { auto* realm = m_vm.current_realm(); u32 bits[2]; bits[0] = m_vector[position++]; bits[1] = m_vector[position++]; double const value = *bit_cast(&bits); m_memory.append(JS::Date::create(*realm, value)); break; } case ValueTag::RegExpObject: { auto pattern = TRY(deserialize_string_primitive(m_vm, m_vector, position)); auto flags = TRY(deserialize_string_primitive(m_vm, m_vector, position)); m_memory.append(TRY(JS::regexp_create(m_vm, move(pattern), move(flags)))); break; } case ValueTag::ArrayBuffer: { auto* realm = m_vm.current_realm(); auto bytes = TRY(deserialize_bytes(m_vm, m_vector, position)); m_memory.append(JS::ArrayBuffer::create(*realm, move(bytes))); break; } case ValueTag::ArrayBufferView: { auto* realm = m_vm.current_realm(); u32 size_bits[2]; size_bits[0] = m_vector[position++]; size_bits[1] = m_vector[position++]; u64 const array_buffer_size = *bit_cast(&size_bits); auto array_buffer_value = TRY(structured_deserialize_impl(m_vm, m_vector.slice(position, array_buffer_size), *realm, m_serialization_memory)); auto& array_buffer = verify_cast(array_buffer_value.as_object()); position += array_buffer_size; auto constructor_name = TRY(deserialize_string(m_vm, m_vector, position)); u32 byte_length = m_vector[position++]; u32 byte_offset = m_vector[position++]; if (constructor_name == "DataView"sv) { m_memory.append(JS::DataView::create(*realm, &array_buffer, byte_length, byte_offset)); } else { u32 array_length = m_vector[position++]; JS::GCPtr typed_array_ptr; #define CREATE_TYPED_ARRAY(ClassName) \ if (constructor_name == #ClassName##sv) \ typed_array_ptr = JS::ClassName::create(*realm, array_length, array_buffer); #define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, Type) \ CREATE_TYPED_ARRAY(ClassName) JS_ENUMERATE_TYPED_ARRAYS #undef __JS_ENUMERATE #undef CREATE_TYPED_ARRAY VERIFY(typed_array_ptr != nullptr); // FIXME: Handle errors better here? Can a fuzzer put weird stuff in the buffer? typed_array_ptr->set_byte_length(byte_length); typed_array_ptr->set_byte_offset(byte_offset); m_memory.append(typed_array_ptr); } break; } case ValueTag::ErrorObject: { auto& realm = *m_vm.current_realm(); auto type = static_cast(m_vector[position++]); auto has_message = static_cast(m_vector[position++]); if (has_message) { auto message = TRY(deserialize_string(m_vm, m_vector, position)); switch (type) { case ErrorType::Error: m_memory.append(JS::Error::create(realm, message)); break; #define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ case ErrorType::ClassName: \ m_memory.append(JS::ClassName::create(realm, message)); \ break; JS_ENUMERATE_NATIVE_ERRORS #undef __JS_ENUMERATE } } else { switch (type) { case ErrorType::Error: m_memory.append(JS::Error::create(realm)); break; #define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ case ErrorType::ClassName: \ m_memory.append(JS::ClassName::create(realm)); \ break; JS_ENUMERATE_NATIVE_ERRORS #undef __JS_ENUMERATE } } break; } default: m_error = "Unsupported type"_fly_string; break; } } return {}; } WebIDL::ExceptionOr result() { if (!m_error.has_value()) return m_memory[0]; return WebIDL::DataCloneError::create(*m_vm.current_realm(), m_error.value()); } private: JS::VM& m_vm; ReadonlySpan m_vector; JS::MarkedVector m_memory; // Index -> JS value Optional m_error; SerializationMemory& m_serialization_memory; static WebIDL::ExceptionOr deserialize_bytes(JS::VM& vm, ReadonlySpan vector, u32& position) { u32 size_bits[2]; size_bits[0] = vector[position++]; size_bits[1] = vector[position++]; u64 const size = *bit_cast(&size_bits); auto bytes = TRY_OR_THROW_OOM(vm, ByteBuffer::create_uninitialized(size)); u64 byte_position = 0; while (position < vector.size() && byte_position < size) { for (u8 i = 0; i < 4; ++i) { bytes[byte_position++] = (vector[position] >> (i * 8) & 0xFF); if (byte_position == size) break; } position++; } return bytes; } static WebIDL::ExceptionOr deserialize_string(JS::VM& vm, ReadonlySpan vector, u32& position) { auto bytes = TRY(deserialize_bytes(vm, vector, position)); return TRY_OR_THROW_OOM(vm, String::from_utf8(StringView { bytes })); } static WebIDL::ExceptionOr> deserialize_string_primitive(JS::VM& vm, ReadonlySpan vector, u32& position) { auto bytes = TRY(deserialize_bytes(vm, vector, position)); return TRY(Bindings::throw_dom_exception_if_needed(vm, [&vm, &bytes]() { return JS::PrimitiveString::create(vm, StringView { bytes }); })); } static WebIDL::ExceptionOr> deserialize_big_int_primitive(JS::VM& vm, ReadonlySpan vector, u32& position) { auto string = TRY(deserialize_string_primitive(vm, vector, position)); auto string_view = TRY(Bindings::throw_dom_exception_if_needed(vm, [&string]() { return string->utf8_string_view(); })); return JS::BigInt::create(vm, ::Crypto::SignedBigInteger::from_base(10, string_view.substring_view(0, string_view.length() - 1))); } }; // https://html.spec.whatwg.org/multipage/structured-data.html#structuredserialize WebIDL::ExceptionOr 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 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 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); } WebIDL::ExceptionOr structured_deserialize_impl(JS::VM& vm, ReadonlySpan serialized, JS::Realm& target_realm, SerializationMemory& memory) { // FIXME: Do the spec steps Deserializer deserializer(vm, target_realm, serialized, memory); TRY(deserializer.deserialize()); return deserializer.result(); } // https://html.spec.whatwg.org/multipage/structured-data.html#structureddeserialize WebIDL::ExceptionOr structured_deserialize(JS::VM& vm, SerializationRecord const& serialized, JS::Realm& target_realm, Optional memory) { if (!memory.has_value()) memory = SerializationMemory {}; return structured_deserialize_impl(vm, serialized.span(), target_realm, *memory); } }