LibWeb: Don't check is_object in every serialization branch

Instead of every branch being of the form:

    if (value.is_object() && is<SomeType>(value.as_object()) {
        auto& some_type = static_cast<SomeType&>(value.as_object());
    }

Let's extract the `is_object` check to an outer branch, and use `as_if`
to check the type.

No functional change, but this makes a future change simpler to review.
This commit is contained in:
Timothy Flynn 2025-07-17 11:36:22 -04:00 committed by Tim Flynn
commit b1cfc96609
Notes: github-actions[bot] 2025-07-18 14:11:02 +00:00

View file

@ -224,152 +224,154 @@ public:
// 6. Let serialized be an uninitialized value. // 6. Let serialized be an uninitialized value.
// 7. If value has a [[BooleanData]] internal slot, then set serialized to { [[Type]]: "Boolean", [[BooleanData]]: value.[[BooleanData]] }. if (value.is_object()) {
if (value.is_object() && is<JS::BooleanObject>(value.as_object())) { auto const& object = value.as_object();
serialize_enum(m_serialized, ValueTag::BooleanObject);
serialize_boolean_object(m_serialized, value);
}
// 8. Otherwise, if value has a [[NumberData]] internal slot, then set serialized to { [[Type]]: "Number", [[NumberData]]: value.[[NumberData]] }. // 7. If value has a [[BooleanData]] internal slot, then set serialized to { [[Type]]: "Boolean", [[BooleanData]]: value.[[BooleanData]] }.
else if (value.is_object() && is<JS::NumberObject>(value.as_object())) { if (is<JS::BooleanObject>(object)) {
serialize_enum(m_serialized, ValueTag::NumberObject); serialize_enum(m_serialized, ValueTag::BooleanObject);
serialize_number_object(m_serialized, value); serialize_boolean_object(m_serialized, value);
} }
// 9. Otherwise, if value has a [[BigIntData]] internal slot, then set serialized to { [[Type]]: "BigInt", [[BigIntData]]: value.[[BigIntData]] }. // 8. Otherwise, if value has a [[NumberData]] internal slot, then set serialized to { [[Type]]: "Number", [[NumberData]]: value.[[NumberData]] }.
else if (value.is_object() && is<JS::BigIntObject>(value.as_object())) { else if (is<JS::NumberObject>(object)) {
serialize_enum(m_serialized, ValueTag::BigIntObject); serialize_enum(m_serialized, ValueTag::NumberObject);
TRY(serialize_big_int_object(m_vm, m_serialized, value)); serialize_number_object(m_serialized, value);
} }
// 10. Otherwise, if value has a [[StringData]] internal slot, then set serialized to { [[Type]]: "String", [[StringData]]: value.[[StringData]] }. // 9. Otherwise, if value has a [[BigIntData]] internal slot, then set serialized to { [[Type]]: "BigInt", [[BigIntData]]: value.[[BigIntData]] }.
else if (value.is_object() && is<JS::StringObject>(value.as_object())) { else if (is<JS::BigIntObject>(object)) {
serialize_enum(m_serialized, ValueTag::StringObject); serialize_enum(m_serialized, ValueTag::BigIntObject);
TRY(serialize_string_object(m_vm, m_serialized, value)); TRY(serialize_big_int_object(m_vm, m_serialized, value));
} }
// 11. Otherwise, if value has a [[DateValue]] internal slot, then set serialized to { [[Type]]: "Date", [[DateValue]]: value.[[DateValue]] }. // 10. Otherwise, if value has a [[StringData]] internal slot, then set serialized to { [[Type]]: "String", [[StringData]]: value.[[StringData]] }.
else if (value.is_object() && is<JS::Date>(value.as_object())) { else if (is<JS::StringObject>(object)) {
serialize_enum(m_serialized, ValueTag::DateObject); serialize_enum(m_serialized, ValueTag::StringObject);
serialize_date_object(m_serialized, value); TRY(serialize_string_object(m_vm, m_serialized, value));
} }
// 12. Otherwise, if value has a [[RegExpMatcher]] internal slot, then set serialized to // 11. Otherwise, if value has a [[DateValue]] internal slot, then set serialized to { [[Type]]: "Date", [[DateValue]]: value.[[DateValue]] }.
// { [[Type]]: "RegExp", [[RegExpMatcher]]: value.[[RegExpMatcher]], [[OriginalSource]]: value.[[OriginalSource]], else if (is<JS::Date>(object)) {
// [[OriginalFlags]]: value.[[OriginalFlags]] }. serialize_enum(m_serialized, ValueTag::DateObject);
else if (value.is_object() && is<JS::RegExpObject>(value.as_object())) { serialize_date_object(m_serialized, value);
serialize_enum(m_serialized, ValueTag::RegExpObject); }
TRY(serialize_reg_exp_object(m_vm, m_serialized, value));
}
// 13. Otherwise, if value has an [[ArrayBufferData]] internal slot, then: // 12. Otherwise, if value has a [[RegExpMatcher]] internal slot, then set serialized to
else if (value.is_object() && is<JS::ArrayBuffer>(value.as_object())) { // { [[Type]]: "RegExp", [[RegExpMatcher]]: value.[[RegExpMatcher]], [[OriginalSource]]: value.[[OriginalSource]],
TRY(serialize_array_buffer(m_vm, m_serialized, static_cast<JS::ArrayBuffer&>(value.as_object()), m_for_storage)); // [[OriginalFlags]]: value.[[OriginalFlags]] }.
} else if (is<JS::RegExpObject>(object)) {
serialize_enum(m_serialized, ValueTag::RegExpObject);
TRY(serialize_reg_exp_object(m_vm, m_serialized, value));
}
// 14. Otherwise, if value has a [[ViewedArrayBuffer]] internal slot, then: // 13. Otherwise, if value has an [[ArrayBufferData]] internal slot, then:
else if (value.is_object() && is<JS::TypedArrayBase>(value.as_object())) { else if (auto const* array_buffer = as_if<JS::ArrayBuffer>(object)) {
TRY(serialize_viewed_array_buffer(m_vm, m_serialized, static_cast<JS::TypedArrayBase&>(value.as_object()), m_for_storage, m_memory)); TRY(serialize_array_buffer(m_vm, m_serialized, *array_buffer, m_for_storage));
} else if (value.is_object() && is<JS::DataView>(value.as_object())) { }
TRY(serialize_viewed_array_buffer(m_vm, m_serialized, static_cast<JS::DataView&>(value.as_object()), m_for_storage, m_memory));
}
// 15. Otherwise, if value has a [[MapData]] internal slot, then: // 14. Otherwise, if value has a [[ViewedArrayBuffer]] internal slot, then:
else if (value.is_object() && is<JS::Map>(value.as_object())) { else if (auto const* typed_array_base = as_if<JS::TypedArrayBase>(object)) {
// 1. Set serialized to { [[Type]]: "Map", [[MapData]]: a new empty List }. TRY(serialize_viewed_array_buffer(m_vm, m_serialized, *typed_array_base, m_for_storage, m_memory));
serialize_enum(m_serialized, ValueTag::MapObject); } else if (auto const* data_view = as_if<JS::DataView>(object)) {
// 2. Set deep to true. TRY(serialize_viewed_array_buffer(m_vm, m_serialized, *data_view, m_for_storage, m_memory));
deep = true; }
}
// 16. Otherwise, if value has a [[SetData]] internal slot, then: // 15. Otherwise, if value has a [[MapData]] internal slot, then:
else if (value.is_object() && is<JS::Set>(value.as_object())) { else if (is<JS::Map>(object)) {
// 1. Set serialized to { [[Type]]: "Set", [[SetData]]: a new empty List }. // 1. Set serialized to { [[Type]]: "Map", [[MapData]]: a new empty List }.
serialize_enum(m_serialized, ValueTag::SetObject); serialize_enum(m_serialized, ValueTag::MapObject);
// 2. Set deep to true. // 2. Set deep to true.
deep = true; deep = true;
} }
// 17. Otherwise, if value has an [[ErrorData]] internal slot and value is not a platform object, then: // 16. Otherwise, if value has a [[SetData]] internal slot, then:
else if (value.is_object() && is<JS::Error>(value.as_object()) && !is<Bindings::PlatformObject>(value.as_object())) { else if (is<JS::Set>(object)) {
// 1. Let name be ? Get(value, "name"). // 1. Set serialized to { [[Type]]: "Set", [[SetData]]: a new empty List }.
auto name_property = TRY(value.as_object().get(m_vm.names.name)); serialize_enum(m_serialized, ValueTag::SetObject);
// 2. Set deep to true.
deep = true;
}
// FIXME: Spec bug - https://github.com/whatwg/html/issues/9923 // 17. Otherwise, if value has an [[ErrorData]] internal slot and value is not a platform object, then:
// MISSING STEP: Set name to ? ToString(name). else if (is<JS::Error>(object) && !is<Bindings::PlatformObject>(object)) {
auto name = TRY(name_property.to_string(m_vm)); // 1. Let name be ? Get(value, "name").
auto name_property = TRY(object.get(m_vm.names.name));
// 2. If name is not one of "Error", "EvalError", "RangeError", "ReferenceError", "SyntaxError", "TypeError", or "URIError", then set name to "Error". // FIXME: Spec bug - https://github.com/whatwg/html/issues/9923
auto type = error_name_to_type(name); // MISSING STEP: Set name to ? ToString(name).
auto name = TRY(name_property.to_string(m_vm));
// 3. Let valueMessageDesc be ? value.[[GetOwnProperty]]("message"). // 2. If name is not one of "Error", "EvalError", "RangeError", "ReferenceError", "SyntaxError", "TypeError", or "URIError", then set name to "Error".
auto value_message_descriptor = TRY(value.as_object().internal_get_own_property(m_vm.names.message)); auto type = error_name_to_type(name);
// 4. Let message be undefined if IsDataDescriptor(valueMessageDesc) is false, and ? ToString(valueMessageDesc.[[Value]]) otherwise. // 3. Let valueMessageDesc be ? value.[[GetOwnProperty]]("message").
Optional<String> message; auto value_message_descriptor = TRY(object.internal_get_own_property(m_vm.names.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 }. // 4. Let message be undefined if IsDataDescriptor(valueMessageDesc) is false, and ? ToString(valueMessageDesc.[[Value]]) otherwise.
// 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. Optional<String> message;
serialize_enum(m_serialized, ValueTag::ErrorObject); if (value_message_descriptor.has_value() && value_message_descriptor->is_data_descriptor())
serialize_enum(m_serialized, type); message = TRY(value_message_descriptor->value->to_string(m_vm));
serialize_primitive_type(m_serialized, message.has_value());
if (message.has_value())
TRY(serialize_string(m_vm, m_serialized, *message));
}
// 18. Otherwise, if value is an Array exotic object, then: // 5. Set serialized to { [[Type]]: "Error", [[Name]]: name, [[Message]]: message }.
else if (value.is_object() && is<JS::Array>(value.as_object())) { // 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.
// 1. Let valueLenDescriptor be ? OrdinaryGetOwnProperty(value, "length"). serialize_enum(m_serialized, ValueTag::ErrorObject);
// 2. Let valueLen be valueLenDescriptor.[[Value]]. serialize_enum(m_serialized, type);
// NON-STANDARD: Array objects in LibJS do not have a real length property, so it must be accessed the usual way serialize_primitive_type(m_serialized, message.has_value());
u64 length = MUST(JS::length_of_array_like(m_vm, value.as_object())); if (message.has_value())
TRY(serialize_string(m_vm, m_serialized, *message));
}
// 3. Set serialized to { [[Type]]: "Array", [[Length]]: valueLen, [[Properties]]: a new empty List }. // 18. Otherwise, if value is an Array exotic object, then:
serialize_enum(m_serialized, ValueTag::ArrayObject); else if (is<JS::Array>(object)) {
serialize_primitive_type(m_serialized, length); // 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));
// 4. Set deep to true. // 3. Set serialized to { [[Type]]: "Array", [[Length]]: valueLen, [[Properties]]: a new empty List }.
deep = true; serialize_enum(m_serialized, ValueTag::ArrayObject);
} serialize_primitive_type(m_serialized, length);
// 19. Otherwise, if value is a platform object that is a serializable object: // 4. Set deep to true.
else if (value.is_object() && is<Bindings::Serializable>(value.as_object())) { deep = true;
auto& serializable = dynamic_cast<Bindings::Serializable&>(value.as_object()); }
// FIXME: 1. If value has a [[Detached]] internal slot whose value is true, then throw a "DataCloneError" DOMException. // 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. // 2. Let typeString be the identifier of the primary interface of value.
// 3. Set serialized to { [[Type]]: typeString }. // 3. Set serialized to { [[Type]]: typeString }.
serialize_enum(m_serialized, ValueTag::SerializableObject); serialize_enum(m_serialized, ValueTag::SerializableObject);
serialize_enum(m_serialized, serializable.serialize_type()); serialize_enum(m_serialized, serializable->serialize_type());
// 4. Set deep to true // 4. Set deep to true
deep = true; deep = true;
} }
// 20. Otherwise, if value is a platform object, then throw a "DataCloneError" DOMException. // 20. Otherwise, if value is a platform object, then throw a "DataCloneError" DOMException.
else if (value.is_object() && is<Bindings::PlatformObject>(value.as_object())) { else if (is<Bindings::PlatformObject>(object)) {
return throw_completion(WebIDL::DataCloneError::create(*m_vm.current_realm(), "Cannot serialize platform objects"_string)); 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. // 21. Otherwise, if IsCallable(value) is true, then throw a "DataCloneError" DOMException.
else if (value.is_function()) { else if (value.is_function()) {
return throw_completion(WebIDL::DataCloneError::create(*m_vm.current_realm(), "Cannot serialize functions"_string)); 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: 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. // 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: // 24. Otherwise:
else { else {
// 1. Set serialized to { [[Type]]: "Object", [[Properties]]: a new empty List }. // 1. Set serialized to { [[Type]]: "Object", [[Properties]]: a new empty List }.
serialize_enum(m_serialized, ValueTag::Object); serialize_enum(m_serialized, ValueTag::Object);
// 2. Set deep to true. // 2. Set deep to true.
deep = true; deep = true;
}
} }
// 25. Set memory[value] to serialized. // 25. Set memory[value] to serialized.
@ -377,20 +379,22 @@ public:
// 26. If deep is true, then: // 26. If deep is true, then:
if (deep) { if (deep) {
auto& object = value.as_object();
// 1. If value has a [[MapData]] internal slot, then: // 1. If value has a [[MapData]] internal slot, then:
if (value.is_object() && is<JS::Map>(value.as_object())) { if (auto const* map = as_if<JS::Map>(object)) {
auto const& map = static_cast<JS::Map const&>(value.as_object());
// 1. Let copiedList be a new empty List. // 1. Let copiedList be a new empty List.
Vector<JS::Value> copied_list; Vector<JS::Value> copied_list;
copied_list.ensure_capacity(map.map_size() * 2); copied_list.ensure_capacity(map->map_size() * 2);
// 2. For each Record { [[Key]], [[Value]] } entry of value.[[MapData]]: // 2. For each Record { [[Key]], [[Value]] } entry of value.[[MapData]]:
for (auto const& entry : static_cast<JS::Map const&>(value.as_object())) { for (auto const& entry : *map) {
// 1. Let copiedEntry be a new Record { [[Key]]: entry.[[Key]], [[Value]]: entry.[[Value]] }. // 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. // 2. If copiedEntry.[[Key]] is not the special value empty, append copiedEntry to copiedList.
copied_list.append(entry.key); copied_list.append(entry.key);
copied_list.append(entry.value); copied_list.append(entry.value);
} }
u64 size = map.map_size(); u64 size = map->map_size();
m_serialized.append(bit_cast<u32*>(&size), 2); m_serialized.append(bit_cast<u32*>(&size), 2);
// 3. For each Record { [[Key]], [[Value]] } entry of copiedList: // 3. For each Record { [[Key]], [[Value]] } entry of copiedList:
for (auto copied_value : copied_list) { for (auto copied_value : copied_list) {
@ -404,17 +408,17 @@ public:
} }
// 2. Otherwise, if value has a [[SetData]] internal slot, then: // 2. Otherwise, if value has a [[SetData]] internal slot, then:
else if (value.is_object() && is<JS::Set>(value.as_object())) { else if (auto const* set = as_if<JS::Set>(object)) {
auto const& set = static_cast<JS::Set const&>(value.as_object());
// 1. Let copiedList be a new empty List. // 1. Let copiedList be a new empty List.
Vector<JS::Value> copied_list; Vector<JS::Value> copied_list;
copied_list.ensure_capacity(set.set_size()); copied_list.ensure_capacity(set->set_size());
// 2. For each entry of value.[[SetData]]: // 2. For each entry of value.[[SetData]]:
for (auto const& entry : static_cast<JS::Set const&>(value.as_object())) { for (auto const& entry : *set) {
// 1. If entry is not the special value empty, append entry to copiedList. // 1. If entry is not the special value empty, append entry to copiedList.
copied_list.append(entry.key); copied_list.append(entry.key);
} }
serialize_primitive_type(m_serialized, set.set_size()); serialize_primitive_type(m_serialized, set->set_size());
// 3. For each entry of copiedList: // 3. For each entry of copiedList:
for (auto copied_value : copied_list) { for (auto copied_value : copied_list) {
// 1. Let serializedEntry be ? StructuredSerializeInternal(entry, forStorage, memory). // 1. Let serializedEntry be ? StructuredSerializeInternal(entry, forStorage, memory).
@ -426,9 +430,8 @@ public:
} }
// 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. // 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 (value.is_object() && is<Bindings::Serializable>(value.as_object())) { else if (auto* serializable = as_if<Bindings::Serializable>(object)) {
auto& serializable = dynamic_cast<Bindings::Serializable&>(value.as_object()); TRY(serializable->serialization_steps(m_serialized, m_for_storage, m_memory));
TRY(serializable.serialization_steps(m_serialized, m_for_storage, m_memory));
} }
// 4. Otherwise, for each key in ! EnumerableOwnProperties(value, key): // 4. Otherwise, for each key in ! EnumerableOwnProperties(value, key):
@ -436,13 +439,13 @@ public:
u64 property_count = 0; u64 property_count = 0;
auto count_offset = m_serialized.size(); auto count_offset = m_serialized.size();
serialize_primitive_type(m_serialized, property_count); serialize_primitive_type(m_serialized, property_count);
for (auto key : MUST(value.as_object().enumerable_own_property_names(JS::Object::PropertyKind::Key))) { for (auto key : MUST(object.enumerable_own_property_names(JS::Object::PropertyKind::Key))) {
auto property_key = MUST(JS::PropertyKey::from_value(m_vm, key)); auto property_key = MUST(JS::PropertyKey::from_value(m_vm, key));
// 1. If ! HasOwnProperty(value, key) is true, then: // 1. If ! HasOwnProperty(value, key) is true, then:
if (MUST(value.as_object().has_own_property(property_key))) { if (MUST(object.has_own_property(property_key))) {
// 1. Let inputValue be ? value.[[Get]](key, value). // 1. Let inputValue be ? value.[[Get]](key, value).
auto input_value = TRY(value.as_object().internal_get(property_key, value)); auto input_value = TRY(object.internal_get(property_key, value));
// 2. Let outputValue be ? StructuredSerializeInternal(inputValue, forStorage, memory). // 2. Let outputValue be ? StructuredSerializeInternal(inputValue, forStorage, memory).
auto output_value = TRY(structured_serialize_internal(m_vm, input_value, m_for_storage, m_memory)); auto output_value = TRY(structured_serialize_internal(m_vm, input_value, m_for_storage, m_memory));