diff --git a/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp b/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp index 3fbb980600..afa32edc54 100644 --- a/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp +++ b/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp @@ -1,14 +1,19 @@ /* * 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 @@ -25,12 +30,28 @@ namespace Web::HTML { // (Should more redundancy be added, e.g., for lengths/positions of values?) enum ValueTag { - // Unused, for ease of catching bugs + // Unused, for ease of catching bugs. Empty, - // Following two u32s are the double value + // 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, + // TODO: Define many more types // This tag or higher are understood to be errors @@ -48,16 +69,34 @@ public: { } - void serialize(JS::Value value) + WebIDL::ExceptionOr serialize(JS::Value value) { - if (value.is_number()) { + 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 { // TODO: Define many more types m_error = "Unsupported type"sv; } + + // Second pass: Update the objects to point to other objects in memory + + return {}; } WebIDL::ExceptionOr> result() @@ -72,6 +111,27 @@ private: SerializationMemory m_memory; // JS value -> index SerializationRecord m_serialized; JS::VM& m_vm; + + WebIDL::ExceptionOr serialize_string(Vector& vector, String const& string) + { + u64 const size = string.code_points().length(); + // Append size of the string to the serialized structure. + TRY_OR_THROW_OOM(m_vm, vector.try_append(bit_cast(&size), 2)); + for (auto code_point : string.code_points()) { + // Append each code point to the serialized structure. + TRY_OR_THROW_OOM(m_vm, vector.try_append(code_point)); + } + return {}; + } + + WebIDL::ExceptionOr serialize_string(Vector& vector, JS::PrimitiveString const& primitive_string) + { + auto string = TRY(Bindings::throw_dom_exception_if_needed(m_vm, [&primitive_string]() { + return primitive_string.utf8_string(); + })); + TRY(serialize_string(vector, string)); + return {}; + } }; class Deserializer { @@ -83,12 +143,24 @@ public: { } - void deserialize() + 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++]; @@ -97,13 +169,22 @@ public: 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; + } default: m_error = "Unsupported type"sv; - return; + break; } } - - // Second pass: Update the objects to point to other objects in memory + return {}; } WebIDL::ExceptionOr result() @@ -118,6 +199,33 @@ private: SerializationRecord const& m_vector; JS::MarkedVector m_memory; // Index -> JS value StringView m_error; + + static WebIDL::ExceptionOr> deserialize_string_primitive(JS::VM& vm, Vector const& vector, u32& position) + { + u32 size_bits[2]; + size_bits[0] = vector[position++]; + size_bits[1] = vector[position++]; + u64 const size = *bit_cast(&size_bits); + + u8 bits[size]; + for (u32 i = 0; i < size; ++i) + bits[i] = vector[position++]; + + ReadonlyBytes const bytes = { bits, size }; + + 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, Vector const& 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 @@ -142,7 +250,7 @@ WebIDL::ExceptionOr structured_serialize_internal(JS::VM& v (void)memory; Serializer serializer(vm); - serializer.serialize(value); + TRY(serializer.serialize(value)); return serializer.result(); // TODO: Avoid several copies of vector } @@ -153,7 +261,7 @@ WebIDL::ExceptionOr structured_deserialize(JS::VM& vm, SerializationR (void)memory; Deserializer deserializer(vm, target_realm, serialized); - deserializer.deserialize(); + TRY(deserializer.deserialize()); return deserializer.result(); }