mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-23 08:00:20 +00:00
LibWeb: Support [de]serialization for {Map, Set, Object, Array} objects
This commit is contained in:
parent
4e89ec7fd5
commit
20734ac335
Notes:
sideshowbarker
2024-07-16 22:17:03 +09:00
Author: https://github.com/IdanHo Commit: https://github.com/SerenityOS/serenity/commit/20734ac335 Pull-request: https://github.com/SerenityOS/serenity/pull/21895
8 changed files with 447 additions and 190 deletions
|
@ -8,5 +8,10 @@ This is a String object
|
|||
/abc/gimsuy
|
||||
Error
|
||||
URIError: hello
|
||||
{"1":2,"a":"b"}
|
||||
1,4,aaaa
|
||||
true
|
||||
1
|
||||
true
|
||||
[object ArrayBuffer]
|
||||
ERROR: DataCloneError: Cannot serialize Symbol
|
|
@ -8,7 +8,7 @@ originParsedBeforeSerializeError.message: Invalid URL for targetOrigin: 'aaaa'
|
|||
originParsedBeforeSerializeError.constructor === window.DOMException: true
|
||||
serializeError instanceof DOMException: true
|
||||
serializeError.name: DataCloneError
|
||||
serializeError.message: Unsupported type
|
||||
serializeError.message: Cannot serialize platform objects
|
||||
serializeError.constructor === window.DOMException: true
|
||||
originIframeError instanceof DOMException: false
|
||||
originIframeError instanceof iframe.contentWindow.DOMException: true
|
||||
|
@ -25,7 +25,7 @@ originParsedBeforeSerializeIframeError.constructor === iframe.contentWindow.DOME
|
|||
serializeIframeError instanceof DOMException: false
|
||||
serializeIframeError instanceof iframe.contentWindow.DOMException: true
|
||||
serializeIframeError.name: DataCloneError
|
||||
serializeIframeError.message: Unsupported type
|
||||
serializeIframeError.message: Cannot serialize platform objects
|
||||
serializeIframeError.constructor === DOMException: false
|
||||
serializeIframeError.constructor === iframe.contentWindow.DOMException: true
|
||||
Message 1 data: undefined
|
||||
|
|
|
@ -11,6 +11,15 @@
|
|||
println(structuredClone(/abc/gimsuy));
|
||||
println(structuredClone(new Error()));
|
||||
println(structuredClone(new URIError("hello")));
|
||||
println(structuredClone(JSON.stringify({"a": "b", 1: 2})));
|
||||
println(structuredClone([1, 4, "aaaa"]));
|
||||
println(structuredClone(new Set(["a", "b", "c"])).has("b"));
|
||||
println(structuredClone(new Map([["a", 0], ["b", 1], ["c", 2]])).get("b"));
|
||||
|
||||
const obj = {"a": 1, "c": 3};
|
||||
obj["b"] = obj;
|
||||
const result = structuredClone(obj);
|
||||
println(result === result["b"]);
|
||||
|
||||
{
|
||||
let arrayBuffer = new ArrayBuffer(6);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* Copyright (c) 2022, Daniel Ehrenberg <dan@littledan.dev>
|
||||
* Copyright (c) 2022, Andrew Kaster <akaster@serenityos.org>
|
||||
* Copyright (c) 2023, Kenneth Myhra <kennethmyhra@serenityos.org>
|
||||
* Copyright (c) 2023, Idan Horowitz <idan.horowitz@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -10,15 +11,18 @@
|
|||
#include <AK/String.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibJS/Forward.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>
|
||||
|
@ -28,8 +32,6 @@
|
|||
|
||||
namespace Web::HTML {
|
||||
|
||||
static WebIDL::ExceptionOr<JS::Value> structured_deserialize_impl(JS::VM& vm, ReadonlySpan<u32> 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).
|
||||
|
@ -85,8 +87,18 @@ enum ValueTag {
|
|||
|
||||
ArrayBufferView,
|
||||
|
||||
MapObject,
|
||||
|
||||
SetObject,
|
||||
|
||||
ErrorObject,
|
||||
|
||||
ArrayObject,
|
||||
|
||||
Object,
|
||||
|
||||
ObjectReference,
|
||||
|
||||
// TODO: Define many more types
|
||||
|
||||
// This tag or higher are understood to be errors
|
||||
|
@ -128,14 +140,13 @@ public:
|
|||
WebIDL::ExceptionOr<SerializationRecord> 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);
|
||||
auto index = m_memory.get(value).value();
|
||||
return Vector<u32> { ValueTag::ObjectReference, index };
|
||||
}
|
||||
|
||||
// 3. Let deep be false.
|
||||
[[maybe_unused]] bool deep = false;
|
||||
auto 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 }.
|
||||
|
@ -169,9 +180,6 @@ public:
|
|||
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<JS::BooleanObject>(value.as_object())) {
|
||||
|
@ -235,6 +243,22 @@ public:
|
|||
TRY(serialize_viewed_array_buffer(m_serialized, static_cast<JS::DataView&>(value.as_object())));
|
||||
}
|
||||
|
||||
// 15. Otherwise, if value has [[MapData]] internal slot, then:
|
||||
else if (value.is_object() && is<JS::Map>(value.as_object())) {
|
||||
// 1. Set serialized to { [[Type]]: "Map", [[MapData]]: a new empty List }.
|
||||
m_serialized.append(ValueTag::MapObject);
|
||||
// 2. Set deep to true.
|
||||
deep = true;
|
||||
}
|
||||
|
||||
// 16. Otherwise, if value has [[SetData]] internal slot, then:
|
||||
else if (value.is_object() && is<JS::Set>(value.as_object())) {
|
||||
// 1. Set serialized to { [[Type]]: "Set", [[SetData]]: a new empty List }.
|
||||
m_serialized.append(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 (value.is_object() && is<JS::Error>(value.as_object()) && !is<Bindings::PlatformObject>(value.as_object())) {
|
||||
// 1. Let name be ? Get(value, "name").
|
||||
|
@ -264,6 +288,23 @@ public:
|
|||
TRY(serialize_string(m_serialized, *message));
|
||||
}
|
||||
|
||||
// 18. Otherwise, if value is an Array exotic object, then:
|
||||
else if (value.is_object() && is<JS::Array>(value.as_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, value.as_object()));
|
||||
|
||||
// 3. Set serialized to { [[Type]]: "Array", [[Length]]: valueLen, [[Properties]]: a new empty List }.
|
||||
m_serialized.append(ValueTag::ArrayObject);
|
||||
m_serialized.append(bit_cast<u32*>(&length), 2);
|
||||
|
||||
// 4. Set deep to true.
|
||||
deep = true;
|
||||
}
|
||||
|
||||
// FIXME: 19. Otherwise, if value is a platform object that is a serializable object:
|
||||
|
||||
// 20. Otherwise, if value is a platform object, then throw a "DataCloneError" DOMException.
|
||||
else if (value.is_object() && is<Bindings::PlatformObject>(value.as_object())) {
|
||||
return throw_completion(WebIDL::DataCloneError::create(*m_vm.current_realm(), "Cannot serialize platform objects"_fly_string));
|
||||
|
@ -278,23 +319,106 @@ public:
|
|||
|
||||
// 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.
|
||||
|
||||
// 15, 16, 18, 19, 24: FIXME: Serialize other data types
|
||||
// 24. Otherwise:
|
||||
else {
|
||||
return throw_completion(WebIDL::DataCloneError::create(*m_vm.current_realm(), "Unsupported type"_fly_string));
|
||||
// 1. Set serialized to { [[Type]]: "Object", [[Properties]]: a new empty List }.
|
||||
m_serialized.append(ValueTag::Object);
|
||||
|
||||
// 2. Set deep to true.
|
||||
deep = true;
|
||||
}
|
||||
|
||||
// 25. Set memory[value] to serialized.
|
||||
auto serialized_end = m_serialized.size();
|
||||
m_memory.set(make_handle(value), { serialized_start, serialized_end });
|
||||
m_memory.set(make_handle(value), m_next_id++);
|
||||
|
||||
// Second pass: Update the objects to point to other objects in memory
|
||||
// 26. If deep is true, then:
|
||||
if (deep) {
|
||||
// 1. If value has a [[MapData]] internal slot, then:
|
||||
if (value.is_object() && is<JS::Map>(value.as_object())) {
|
||||
auto const& map = static_cast<JS::Map const&>(value.as_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 : static_cast<JS::Map const&>(value.as_object())) {
|
||||
// 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);
|
||||
}
|
||||
u64 size = map.map_size();
|
||||
m_serialized.append(bit_cast<u32*>(&size), 2);
|
||||
// 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]].
|
||||
m_serialized.extend(serialized_value);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Otherwise, if value has a [[SetData]] internal slot, then:
|
||||
else if (value.is_object() && is<JS::Set>(value.as_object())) {
|
||||
auto const& set = static_cast<JS::Set const&>(value.as_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 : static_cast<JS::Set const&>(value.as_object())) {
|
||||
// 1. If entry is not the special value empty, append entry to copiedList.
|
||||
copied_list.append(entry.key);
|
||||
}
|
||||
u64 size = set.set_size();
|
||||
m_serialized.append(bit_cast<u32*>(&size), 2);
|
||||
// 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]].
|
||||
m_serialized.extend(serialized_value);
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: 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.
|
||||
|
||||
// 4. Otherwise, for each key in ! EnumerableOwnProperties(value, key):
|
||||
else {
|
||||
u64 property_count = 0;
|
||||
auto count_offset = m_serialized.size();
|
||||
m_serialized.append(bit_cast<u32*>(&property_count), 2);
|
||||
for (auto key : MUST(value.as_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(value.as_object().has_own_property(property_key))) {
|
||||
// 1. Let inputValue be ? value.[[Get]](key, value).
|
||||
auto input_value = TRY(value.as_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]].
|
||||
TRY(serialize_string(m_serialized, key.as_string()));
|
||||
m_serialized.extend(output_value);
|
||||
|
||||
property_count++;
|
||||
}
|
||||
}
|
||||
memcpy(m_serialized.data() + count_offset, &property_count, sizeof(property_count));
|
||||
}
|
||||
}
|
||||
|
||||
// 27. Return serialized.
|
||||
return m_serialized;
|
||||
}
|
||||
|
||||
private:
|
||||
JS::VM& m_vm;
|
||||
SerializationMemory& m_memory; // JS value -> index
|
||||
u32 m_next_id { 0 };
|
||||
SerializationRecord m_serialized;
|
||||
bool m_for_storage { false };
|
||||
|
||||
|
@ -408,8 +532,6 @@ private:
|
|||
// [[ArrayBufferSerialized]]: bufferSerialized, [[ByteLength]]: value.[[ByteLength]], [[ByteOffset]]: value.[[ByteOffset]] }.
|
||||
if constexpr (IsSame<ViewType, JS::DataView>) {
|
||||
vector.append(ValueTag::ArrayBufferView);
|
||||
u64 const serialized_buffer_size = buffer_serialized.size();
|
||||
vector.append(bit_cast<u32*>(&serialized_buffer_size), 2);
|
||||
vector.extend(move(buffer_serialized)); // [[ArrayBufferSerialized]]
|
||||
TRY(serialize_string(vector, "DataView"_string)); // [[Constructor]]
|
||||
vector.append(view.byte_length());
|
||||
|
@ -424,8 +546,6 @@ private:
|
|||
// [[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<u32*>(&serialized_buffer_size), 2);
|
||||
vector.extend(move(buffer_serialized)); // [[ArrayBufferSerialized]]
|
||||
TRY(serialize_string(vector, view.element_name())); // [[Constructor]]
|
||||
vector.append(view.byte_length());
|
||||
|
@ -438,187 +558,320 @@ private:
|
|||
|
||||
class Deserializer {
|
||||
public:
|
||||
Deserializer(JS::VM& vm, JS::Realm& target_realm, ReadonlySpan<u32> v, SerializationMemory& serialization_memory)
|
||||
Deserializer(JS::VM& vm, JS::Realm& target_realm, ReadonlySpan<u32> serialized, DeserializationMemory& memory)
|
||||
: m_vm(vm)
|
||||
, m_vector(v)
|
||||
, m_memory(target_realm.heap())
|
||||
, m_serialization_memory(serialization_memory)
|
||||
, m_serialized(serialized)
|
||||
, m_memory(memory)
|
||||
{
|
||||
VERIFY(vm.current_realm() == &target_realm);
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<void> deserialize()
|
||||
// https://html.spec.whatwg.org/multipage/structured-data.html#structureddeserialize
|
||||
WebIDL::ExceptionOr<JS::Value> 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<bool>(m_vector[position++])));
|
||||
break;
|
||||
}
|
||||
case ValueTag::NumberPrimitive: {
|
||||
u32 bits[2];
|
||||
bits[0] = m_vector[position++];
|
||||
bits[1] = m_vector[position++];
|
||||
double value = *bit_cast<double*>(&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<bool>(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<double*>(&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<double*>(&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<u64*>(&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<JS::ArrayBuffer>(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++];
|
||||
auto tag = m_serialized[m_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<JS::TypedArrayBase> typed_array_ptr;
|
||||
// 2. If memory[serialized] exists, then return memory[serialized].
|
||||
if (tag == ValueTag::ObjectReference) {
|
||||
auto index = m_serialized[m_position++];
|
||||
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;
|
||||
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(static_cast<bool>(m_serialized[m_position++]));
|
||||
is_primitive = true;
|
||||
break;
|
||||
}
|
||||
case ValueTag::NumberPrimitive: {
|
||||
u32 bits[2] = {};
|
||||
bits[0] = m_serialized[m_position++];
|
||||
bits[1] = m_serialized[m_position++];
|
||||
auto double_value = *bit_cast<double*>(&bits);
|
||||
value = JS::Value(double_value);
|
||||
is_primitive = true;
|
||||
break;
|
||||
}
|
||||
case ValueTag::BigIntPrimitive: {
|
||||
auto big_int = TRY(deserialize_big_int_primitive(m_vm, m_serialized, m_position));
|
||||
value = JS::Value { big_int };
|
||||
is_primitive = true;
|
||||
break;
|
||||
}
|
||||
case ValueTag::StringPrimitive: {
|
||||
auto string = TRY(deserialize_string_primitive(m_vm, m_serialized, m_position));
|
||||
value = JS::Value { 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 BooleanObject: {
|
||||
auto* realm = m_vm.current_realm();
|
||||
auto bool_value = static_cast<bool>(m_serialized[m_position++]);
|
||||
value = JS::BooleanObject::create(*realm, bool_value);
|
||||
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: {
|
||||
auto* realm = m_vm.current_realm();
|
||||
u32 bits[2];
|
||||
bits[0] = m_serialized[m_position++];
|
||||
bits[1] = m_serialized[m_position++];
|
||||
auto double_value = *bit_cast<double*>(&bits);
|
||||
value = JS::NumberObject::create(*realm, double_value);
|
||||
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: {
|
||||
auto* realm = m_vm.current_realm();
|
||||
auto big_int = TRY(deserialize_big_int_primitive(m_vm, m_serialized, m_position));
|
||||
value = JS::BigIntObject::create(*realm, 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: {
|
||||
auto* realm = m_vm.current_realm();
|
||||
auto string = TRY(deserialize_string_primitive(m_vm, m_serialized, m_position));
|
||||
value = JS::StringObject::create(*realm, 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: {
|
||||
auto* realm = m_vm.current_realm();
|
||||
u32 bits[2];
|
||||
bits[0] = m_serialized[m_position++];
|
||||
bits[1] = m_serialized[m_position++];
|
||||
auto double_value = *bit_cast<double*>(&bits);
|
||||
value = JS::Date::create(*realm, double_value);
|
||||
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 = TRY(deserialize_string_primitive(m_vm, m_serialized, m_position));
|
||||
auto flags = TRY(deserialize_string_primitive(m_vm, m_serialized, m_position));
|
||||
value = TRY(JS::regexp_create(m_vm, move(pattern), move(flags)));
|
||||
break;
|
||||
}
|
||||
// FIXME: 12. Otherwise, if serialized.[[Type]] is "SharedArrayBuffer", then:
|
||||
// FIXME: 13. Otherwise, if serialized.[[Type]] is "GrowableSharedArrayBuffer", then:
|
||||
// 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* realm = m_vm.current_realm();
|
||||
// If this throws an exception, catch it, and then throw a "DataCloneError" DOMException.
|
||||
auto bytes_or_error = deserialize_bytes(m_vm, m_serialized, m_position);
|
||||
if (bytes_or_error.is_error())
|
||||
return WebIDL::DataCloneError::create(*m_vm.current_realm(), "out of memory"_fly_string);
|
||||
value = JS::ArrayBuffer::create(*realm, bytes_or_error.release_value());
|
||||
break;
|
||||
}
|
||||
// FIXME: 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]].
|
||||
// 16. Otherwise, if serialized.[[Type]] is "ArrayBufferView", then:
|
||||
case ValueTag::ArrayBufferView: {
|
||||
auto* realm = m_vm.current_realm();
|
||||
auto array_buffer_value = TRY(deserialize());
|
||||
auto& array_buffer = verify_cast<JS::ArrayBuffer>(array_buffer_value.as_object());
|
||||
auto constructor_name = TRY(deserialize_string(m_vm, m_serialized, m_position));
|
||||
u32 byte_length = m_serialized[m_position++];
|
||||
u32 byte_offset = m_serialized[m_position++];
|
||||
|
||||
if (constructor_name == "DataView"sv) {
|
||||
value = JS::DataView::create(*realm, &array_buffer, byte_length, byte_offset);
|
||||
} else {
|
||||
u32 array_length = m_serialized[m_position++];
|
||||
JS::GCPtr<JS::TypedArrayBase> 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
|
||||
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;
|
||||
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);
|
||||
value = typed_array_ptr;
|
||||
}
|
||||
case ValueTag::ErrorObject: {
|
||||
auto& realm = *m_vm.current_realm();
|
||||
auto type = static_cast<ErrorType>(m_vector[position++]);
|
||||
auto has_message = static_cast<bool>(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;
|
||||
break;
|
||||
}
|
||||
// 17. Otherwise, if serialized.[[Type]] is "Map", then:
|
||||
case ValueTag::MapObject: {
|
||||
auto& realm = *m_vm.current_realm();
|
||||
// 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: {
|
||||
auto& realm = *m_vm.current_realm();
|
||||
// 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: {
|
||||
auto& realm = *m_vm.current_realm();
|
||||
// 1. Let outputProto be targetRealm.[[Intrinsics]].[[%Array.prototype%]].
|
||||
// 2. Set value to ! ArrayCreate(serialized.[[Length]], outputProto).
|
||||
auto length = read_u64();
|
||||
value = MUST(JS::Array::create(realm, length));
|
||||
// 3. Set deep to true.
|
||||
deep = true;
|
||||
break;
|
||||
}
|
||||
// 20. Otherwise, if serialized.[[Type]] is "Object", then:
|
||||
case ValueTag::Object: {
|
||||
auto& realm = *m_vm.current_realm();
|
||||
// 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& realm = *m_vm.current_realm();
|
||||
auto type = static_cast<ErrorType>(m_serialized[m_position++]);
|
||||
auto has_message = static_cast<bool>(m_serialized[m_position++]);
|
||||
if (has_message) {
|
||||
auto message = TRY(deserialize_string(m_vm, m_serialized, m_position));
|
||||
switch (type) {
|
||||
case ErrorType::Error:
|
||||
value = 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)); \
|
||||
value = JS::ClassName::create(realm, message); \
|
||||
break;
|
||||
JS_ENUMERATE_NATIVE_ERRORS
|
||||
JS_ENUMERATE_NATIVE_ERRORS
|
||||
#undef __JS_ENUMERATE
|
||||
}
|
||||
} else {
|
||||
switch (type) {
|
||||
case ErrorType::Error:
|
||||
m_memory.append(JS::Error::create(realm));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (type) {
|
||||
case ErrorType::Error:
|
||||
value = JS::Error::create(realm);
|
||||
break;
|
||||
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
|
||||
case ErrorType::ClassName: \
|
||||
m_memory.append(JS::ClassName::create(realm)); \
|
||||
value = JS::ClassName::create(realm); \
|
||||
break;
|
||||
JS_ENUMERATE_NATIVE_ERRORS
|
||||
JS_ENUMERATE_NATIVE_ERRORS
|
||||
#undef __JS_ENUMERATE
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
m_error = "Unsupported type"_fly_string;
|
||||
break;
|
||||
break;
|
||||
}
|
||||
// 22. Otherwise:
|
||||
default:
|
||||
return WebIDL::DataCloneError::create(*m_vm.current_realm(), "Unsupported type"_fly_string);
|
||||
}
|
||||
|
||||
// 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 = static_cast<JS::Map&>(value.as_object());
|
||||
auto length = read_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 = static_cast<JS::Set&>(value.as_object());
|
||||
auto length = read_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 = read_u64();
|
||||
// 1. For each Record { [[Key]], [[Value]] } entry of serialized.[[Properties]]:
|
||||
for (u64 i = 0u; i < length; ++i) {
|
||||
auto key = TRY(deserialize_string(m_vm, m_serialized, m_position));
|
||||
|
||||
// 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.to_deprecated_string(), deserialized_value));
|
||||
|
||||
// 3. Assert: result is true.
|
||||
VERIFY(result);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Otherwise:
|
||||
else {
|
||||
// FIXME: 1. Perform the appropriate deserialization steps for the interface identified by serialized.[[Type]], given serialized, value, and targetRealm.
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<JS::Value> result()
|
||||
{
|
||||
if (!m_error.has_value())
|
||||
return m_memory[0];
|
||||
return WebIDL::DataCloneError::create(*m_vm.current_realm(), m_error.value());
|
||||
// 25. Return value.
|
||||
return value;
|
||||
}
|
||||
|
||||
private:
|
||||
JS::VM& m_vm;
|
||||
ReadonlySpan<u32> m_vector;
|
||||
ReadonlySpan<u32> m_serialized;
|
||||
size_t m_position { 0 };
|
||||
JS::MarkedVector<JS::Value> m_memory; // Index -> JS value
|
||||
Optional<FlyString> m_error;
|
||||
SerializationMemory& m_serialization_memory;
|
||||
|
||||
static WebIDL::ExceptionOr<ByteBuffer> deserialize_bytes(JS::VM& vm, ReadonlySpan<u32> vector, u32& position)
|
||||
u64 read_u64()
|
||||
{
|
||||
u64 value;
|
||||
memcpy(&value, m_serialized.offset_pointer(m_position), sizeof(value));
|
||||
m_position += 2;
|
||||
return value;
|
||||
}
|
||||
|
||||
static WebIDL::ExceptionOr<ByteBuffer> deserialize_bytes(JS::VM& vm, ReadonlySpan<u32> vector, size_t& position)
|
||||
{
|
||||
u32 size_bits[2];
|
||||
size_bits[0] = vector[position++];
|
||||
|
@ -638,13 +891,13 @@ private:
|
|||
return bytes;
|
||||
}
|
||||
|
||||
static WebIDL::ExceptionOr<String> deserialize_string(JS::VM& vm, ReadonlySpan<u32> vector, u32& position)
|
||||
static WebIDL::ExceptionOr<String> deserialize_string(JS::VM& vm, ReadonlySpan<u32> vector, size_t& position)
|
||||
{
|
||||
auto bytes = TRY(deserialize_bytes(vm, vector, position));
|
||||
return TRY_OR_THROW_OOM(vm, String::from_utf8(StringView { bytes }));
|
||||
}
|
||||
|
||||
static WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::PrimitiveString>> deserialize_string_primitive(JS::VM& vm, ReadonlySpan<u32> vector, u32& position)
|
||||
static WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::PrimitiveString>> deserialize_string_primitive(JS::VM& vm, ReadonlySpan<u32> vector, size_t& position)
|
||||
{
|
||||
auto bytes = TRY(deserialize_bytes(vm, vector, position));
|
||||
|
||||
|
@ -653,7 +906,7 @@ private:
|
|||
}));
|
||||
}
|
||||
|
||||
static WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::BigInt>> deserialize_big_int_primitive(JS::VM& vm, ReadonlySpan<u32> vector, u32& position)
|
||||
static WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::BigInt>> deserialize_big_int_primitive(JS::VM& vm, ReadonlySpan<u32> vector, size_t& position)
|
||||
{
|
||||
auto string = TRY(deserialize_string_primitive(vm, vector, position));
|
||||
auto string_view = TRY(Bindings::throw_dom_exception_if_needed(vm, [&string]() {
|
||||
|
@ -689,21 +942,14 @@ WebIDL::ExceptionOr<SerializationRecord> structured_serialize_internal(JS::VM& v
|
|||
return serializer.serialize(value);
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<JS::Value> structured_deserialize_impl(JS::VM& vm, ReadonlySpan<u32> 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<JS::Value> structured_deserialize(JS::VM& vm, SerializationRecord const& serialized, JS::Realm& target_realm, Optional<SerializationMemory> memory)
|
||||
WebIDL::ExceptionOr<JS::Value> structured_deserialize(JS::VM& vm, SerializationRecord const& serialized, JS::Realm& target_realm, Optional<DeserializationMemory> memory)
|
||||
{
|
||||
if (!memory.has_value())
|
||||
memory = SerializationMemory {};
|
||||
memory = DeserializationMemory { vm.heap() };
|
||||
|
||||
return structured_deserialize_impl(vm, serialized.span(), target_realm, *memory);
|
||||
Deserializer deserializer(vm, target_realm, serialized.span(), *memory);
|
||||
return deserializer.deserialize();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,17 +21,14 @@
|
|||
namespace Web::HTML {
|
||||
|
||||
using SerializationRecord = Vector<u32>;
|
||||
struct SerializationRange {
|
||||
u64 start = 0;
|
||||
u64 end = 0;
|
||||
};
|
||||
using SerializationMemory = HashMap<JS::Handle<JS::Value>, SerializationRange>;
|
||||
using SerializationMemory = HashMap<JS::Handle<JS::Value>, u32>;
|
||||
using DeserializationMemory = JS::MarkedVector<JS::Value>;
|
||||
|
||||
WebIDL::ExceptionOr<SerializationRecord> structured_serialize(JS::VM& vm, JS::Value);
|
||||
WebIDL::ExceptionOr<SerializationRecord> structured_serialize_for_storage(JS::VM& vm, JS::Value);
|
||||
WebIDL::ExceptionOr<SerializationRecord> structured_serialize_internal(JS::VM& vm, JS::Value, bool for_storage, SerializationMemory&);
|
||||
|
||||
WebIDL::ExceptionOr<JS::Value> structured_deserialize(JS::VM& vm, SerializationRecord const& serialized, JS::Realm& target_realm, Optional<SerializationMemory>);
|
||||
WebIDL::ExceptionOr<JS::Value> structured_deserialize(JS::VM& vm, SerializationRecord const& serialized, JS::Realm& target_realm, Optional<DeserializationMemory>);
|
||||
|
||||
// TODO: structured_[de]serialize_with_transfer
|
||||
|
||||
|
|
|
@ -1057,7 +1057,7 @@ WebIDL::ExceptionOr<void> Window::window_post_message_steps(JS::Value message, W
|
|||
// FIXME: Don't use a temporary execution context here.
|
||||
auto& settings_object = Bindings::host_defined_environment_settings_object(target_realm);
|
||||
auto temporary_execution_context = TemporaryExecutionContext { settings_object };
|
||||
auto deserialize_record_or_error = structured_deserialize(vm(), serialize_with_transfer_result, target_realm, Optional<HTML::SerializationMemory> {});
|
||||
auto deserialize_record_or_error = structured_deserialize(vm(), serialize_with_transfer_result, target_realm, Optional<HTML::DeserializationMemory> {});
|
||||
|
||||
// If this throws an exception, catch it, fire an event named messageerror at targetWindow, using MessageEvent,
|
||||
// with the origin attribute initialized to origin and the source attribute initialized to source, and then return.
|
||||
|
|
|
@ -283,7 +283,7 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<UserTiming::PerformanceMeasure>> Performanc
|
|||
auto record = TRY(HTML::structured_serialize(vm, start_or_measure_options_dictionary_object->detail));
|
||||
|
||||
// 2. Set entry's detail to the result of calling the StructuredDeserialize algorithm on record and the current realm.
|
||||
detail = TRY(HTML::structured_deserialize(vm, record, realm, Optional<HTML::SerializationMemory> {}));
|
||||
detail = TRY(HTML::structured_deserialize(vm, record, realm, Optional<HTML::DeserializationMemory> {}));
|
||||
}
|
||||
|
||||
// 2. Otherwise, set it to null.
|
||||
|
|
|
@ -84,7 +84,7 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<PerformanceMark>> PerformanceMark::construc
|
|||
auto record = TRY(HTML::structured_serialize(vm, mark_options.detail));
|
||||
|
||||
// 2. Set entry's detail to the result of calling the StructuredDeserialize algorithm on record and the current realm.
|
||||
detail = TRY(HTML::structured_deserialize(vm, record, realm, Optional<HTML::SerializationMemory> {}));
|
||||
detail = TRY(HTML::structured_deserialize(vm, record, realm, Optional<HTML::DeserializationMemory> {}));
|
||||
}
|
||||
|
||||
// 2. Create a new PerformanceMark object (entry) with the current global object's realm.
|
||||
|
|
Loading…
Reference in a new issue