LibWeb: Use enum for serialization and reimplement interface exposure

Our currently implementation of structured serialization has a design
flaw, where if the serialized/transferred type was not used in the
destination realm, it would not be seen as exposed and thus we would
not re-create the type on the other side.

This is very common, for example, transferring a MessagePort to a just
inserted iframe, or the just inserted iframe transferring a MessagePort
to it's parent. This is what Google reCAPTCHA does.

This flaw occurred due to relying on lazily populated HashMaps of
constructors, namespaces and interfaces. This commit changes it so that
per-type "is exposed" implementations are generated.

Since it no longer relies on interface name strings, this commit
changes serializable types to indicate their type with an enum,
in line with how transferrable types indicate their type.

This makes Google reCAPTCHA work on https://www.google.com/recaptcha/api2/demo
It currently doesn't work on non-Google origins due to a separate
same-origin policy bug.
This commit is contained in:
Luke Wilde 2025-07-14 17:15:09 +01:00 committed by Tim Flynn
commit d08d6b08d3
Notes: github-actions[bot] 2025-07-15 13:21:14 +00:00
25 changed files with 356 additions and 130 deletions

View file

@ -24,11 +24,6 @@ void Intrinsics::visit_edges(JS::Cell::Visitor& visitor)
visitor.visit(m_realm); visitor.visit(m_realm);
} }
bool Intrinsics::is_exposed(StringView name) const
{
return m_constructors.contains(name) || m_prototypes.contains(name) || m_namespaces.contains(name);
}
Intrinsics& host_defined_intrinsics(JS::Realm& realm) Intrinsics& host_defined_intrinsics(JS::Realm& realm)
{ {
ASSERT(realm.host_defined()); ASSERT(realm.host_defined());

View file

@ -66,7 +66,8 @@ public:
return *m_constructors.find(class_name)->value; return *m_constructors.find(class_name)->value;
} }
bool is_exposed(StringView name) const; template<typename PrototypeType>
bool is_interface_exposed(JS::Realm&) const;
private: private:
virtual void visit_edges(JS::Cell::Visitor&) override; virtual void visit_edges(JS::Cell::Visitor&) override;

View file

@ -16,7 +16,7 @@ class Serializable {
public: public:
virtual ~Serializable() = default; virtual ~Serializable() = default;
virtual StringView interface_name() const = 0; virtual HTML::SerializeType serialize_type() const = 0;
// https://html.spec.whatwg.org/multipage/structured-data.html#serialization-steps // https://html.spec.whatwg.org/multipage/structured-data.html#serialization-steps
virtual WebIDL::ExceptionOr<void> serialization_steps(HTML::SerializationRecord&, bool for_storage, HTML::SerializationMemory&) = 0; virtual WebIDL::ExceptionOr<void> serialization_steps(HTML::SerializationRecord&, bool for_storage, HTML::SerializationMemory&) = 0;

View file

@ -47,7 +47,7 @@ public:
InternalKeyData const& handle() const { return m_key_data; } InternalKeyData const& handle() const { return m_key_data; }
String algorithm_name() const; String algorithm_name() const;
virtual StringView interface_name() const override { return "CryptoKey"sv; } virtual HTML::SerializeType serialize_type() const override { return HTML::SerializeType::CryptoKey; }
virtual WebIDL::ExceptionOr<void> serialization_steps(HTML::SerializationRecord& record, bool for_storage, HTML::SerializationMemory&) override; virtual WebIDL::ExceptionOr<void> serialization_steps(HTML::SerializationRecord& record, bool for_storage, HTML::SerializationMemory&) override;
virtual WebIDL::ExceptionOr<void> deserialization_steps(ReadonlySpan<u32> const& record, size_t& position, HTML::DeserializationMemory&) override; virtual WebIDL::ExceptionOr<void> deserialization_steps(ReadonlySpan<u32> const& record, size_t& position, HTML::DeserializationMemory&) override;

View file

@ -56,7 +56,7 @@ public:
GC::Ref<Streams::ReadableStream> get_stream(); GC::Ref<Streams::ReadableStream> get_stream();
virtual StringView interface_name() const override { return "Blob"sv; } virtual HTML::SerializeType serialize_type() const override { return HTML::SerializeType::Blob; }
virtual WebIDL::ExceptionOr<void> serialization_steps(HTML::SerializationRecord& record, bool for_storage, HTML::SerializationMemory&) override; virtual WebIDL::ExceptionOr<void> serialization_steps(HTML::SerializationRecord& record, bool for_storage, HTML::SerializationMemory&) override;
virtual WebIDL::ExceptionOr<void> deserialization_steps(ReadonlySpan<u32> const& record, size_t& position, HTML::DeserializationMemory&) override; virtual WebIDL::ExceptionOr<void> deserialization_steps(ReadonlySpan<u32> const& record, size_t& position, HTML::DeserializationMemory&) override;

View file

@ -30,7 +30,7 @@ public:
// https://w3c.github.io/FileAPI/#dfn-lastModified // https://w3c.github.io/FileAPI/#dfn-lastModified
i64 last_modified() const { return m_last_modified; } i64 last_modified() const { return m_last_modified; }
virtual StringView interface_name() const override { return "File"sv; } virtual HTML::SerializeType serialize_type() const override { return HTML::SerializeType::File; }
virtual WebIDL::ExceptionOr<void> serialization_steps(HTML::SerializationRecord& record, bool for_storage, HTML::SerializationMemory&) override; virtual WebIDL::ExceptionOr<void> serialization_steps(HTML::SerializationRecord& record, bool for_storage, HTML::SerializationMemory&) override;
virtual WebIDL::ExceptionOr<void> deserialization_steps(ReadonlySpan<u32> const&, size_t& position, HTML::DeserializationMemory&) override; virtual WebIDL::ExceptionOr<void> deserialization_steps(ReadonlySpan<u32> const&, size_t& position, HTML::DeserializationMemory&) override;

View file

@ -45,7 +45,7 @@ public:
virtual Optional<JS::Value> item_value(size_t index) const override; virtual Optional<JS::Value> item_value(size_t index) const override;
virtual StringView interface_name() const override { return "FileList"sv; } virtual HTML::SerializeType serialize_type() const override { return HTML::SerializeType::FileList; }
virtual WebIDL::ExceptionOr<void> serialization_steps(HTML::SerializationRecord& serialized, bool for_storage, HTML::SerializationMemory&) override; virtual WebIDL::ExceptionOr<void> serialization_steps(HTML::SerializationRecord& serialized, bool for_storage, HTML::SerializationMemory&) override;
virtual WebIDL::ExceptionOr<void> deserialization_steps(ReadonlySpan<u32> const& serialized, size_t& position, HTML::DeserializationMemory&) override; virtual WebIDL::ExceptionOr<void> deserialization_steps(ReadonlySpan<u32> const& serialized, size_t& position, HTML::DeserializationMemory&) override;

View file

@ -69,7 +69,7 @@ public:
WebIDL::ExceptionOr<GC::Ref<DOMMatrix>> set_matrix_value(String const& transform_list); WebIDL::ExceptionOr<GC::Ref<DOMMatrix>> set_matrix_value(String const& transform_list);
virtual StringView interface_name() const override { return "DOMMatrix"sv; } virtual HTML::SerializeType serialize_type() const override { return HTML::SerializeType::DOMMatrix; }
private: private:
DOMMatrix(JS::Realm&, double m11, double m12, double m21, double m22, double m41, double m42); DOMMatrix(JS::Realm&, double m11, double m12, double m21, double m22, double m41, double m42);

View file

@ -115,7 +115,7 @@ public:
WebIDL::ExceptionOr<String> to_string() const; WebIDL::ExceptionOr<String> to_string() const;
virtual StringView interface_name() const override { return "DOMMatrixReadOnly"sv; } virtual HTML::SerializeType serialize_type() const override { return HTML::SerializeType::DOMMatrixReadOnly; }
virtual WebIDL::ExceptionOr<void> serialization_steps(HTML::SerializationRecord&, bool for_storage, HTML::SerializationMemory&) override; virtual WebIDL::ExceptionOr<void> serialization_steps(HTML::SerializationRecord&, bool for_storage, HTML::SerializationMemory&) override;
virtual WebIDL::ExceptionOr<void> deserialization_steps(ReadonlySpan<u32> const& record, size_t& position, HTML::DeserializationMemory&) override; virtual WebIDL::ExceptionOr<void> deserialization_steps(ReadonlySpan<u32> const& record, size_t& position, HTML::DeserializationMemory&) override;

View file

@ -34,7 +34,7 @@ public:
void set_z(double z) { m_z = z; } void set_z(double z) { m_z = z; }
void set_w(double w) { m_w = w; } void set_w(double w) { m_w = w; }
virtual StringView interface_name() const override { return "DOMPoint"sv; } virtual HTML::SerializeType serialize_type() const override { return HTML::SerializeType::DOMPoint; }
private: private:
DOMPoint(JS::Realm&, double x, double y, double z, double w); DOMPoint(JS::Realm&, double x, double y, double z, double w);

View file

@ -44,7 +44,7 @@ public:
WebIDL::ExceptionOr<GC::Ref<DOMPoint>> matrix_transform(DOMMatrixInit&) const; WebIDL::ExceptionOr<GC::Ref<DOMPoint>> matrix_transform(DOMMatrixInit&) const;
virtual StringView interface_name() const override { return "DOMPointReadOnly"sv; } virtual HTML::SerializeType serialize_type() const override { return HTML::SerializeType::DOMPointReadOnly; }
virtual WebIDL::ExceptionOr<void> serialization_steps(HTML::SerializationRecord&, bool for_storage, HTML::SerializationMemory&) override; virtual WebIDL::ExceptionOr<void> serialization_steps(HTML::SerializationRecord&, bool for_storage, HTML::SerializationMemory&) override;
virtual WebIDL::ExceptionOr<void> deserialization_steps(ReadonlySpan<u32> const&, size_t& position, HTML::DeserializationMemory&) override; virtual WebIDL::ExceptionOr<void> deserialization_steps(ReadonlySpan<u32> const&, size_t& position, HTML::DeserializationMemory&) override;

View file

@ -44,7 +44,7 @@ public:
GC::Ref<DOMRect> get_bounds() const; GC::Ref<DOMRect> get_bounds() const;
virtual StringView interface_name() const override { return "DOMQuad"sv; } virtual HTML::SerializeType serialize_type() const override { return HTML::SerializeType::DOMQuad; }
virtual WebIDL::ExceptionOr<void> serialization_steps(HTML::SerializationRecord&, bool for_storage, HTML::SerializationMemory&) override; virtual WebIDL::ExceptionOr<void> serialization_steps(HTML::SerializationRecord&, bool for_storage, HTML::SerializationMemory&) override;
virtual WebIDL::ExceptionOr<void> deserialization_steps(ReadonlySpan<u32> const&, size_t& position, HTML::DeserializationMemory&) override; virtual WebIDL::ExceptionOr<void> deserialization_steps(ReadonlySpan<u32> const&, size_t& position, HTML::DeserializationMemory&) override;

View file

@ -33,7 +33,7 @@ public:
void set_width(double width) { m_rect.set_width(width); } void set_width(double width) { m_rect.set_width(width); }
void set_height(double height) { m_rect.set_height(height); } void set_height(double height) { m_rect.set_height(height); }
virtual StringView interface_name() const override { return "DOMRect"sv; } virtual HTML::SerializeType serialize_type() const override { return HTML::SerializeType::DOMRect; }
private: private:
DOMRect(JS::Realm&, double x, double y, double width, double height); DOMRect(JS::Realm&, double x, double y, double width, double height);

View file

@ -69,7 +69,7 @@ public:
return min(x(), x() + width()); return min(x(), x() + width());
} }
virtual StringView interface_name() const override { return "DOMRectReadOnly"sv; } virtual HTML::SerializeType serialize_type() const override { return HTML::SerializeType::DOMRectReadOnly; }
virtual WebIDL::ExceptionOr<void> serialization_steps(HTML::SerializationRecord&, bool for_storage, HTML::SerializationMemory&) override; virtual WebIDL::ExceptionOr<void> serialization_steps(HTML::SerializationRecord&, bool for_storage, HTML::SerializationMemory&) override;
virtual WebIDL::ExceptionOr<void> deserialization_steps(ReadonlySpan<u32> const&, size_t& position, HTML::DeserializationMemory&) override; virtual WebIDL::ExceptionOr<void> deserialization_steps(ReadonlySpan<u32> const&, size_t& position, HTML::DeserializationMemory&) override;

View file

@ -34,7 +34,7 @@ public:
virtual ~ImageBitmap() override = default; virtual ~ImageBitmap() override = default;
// ^Web::Bindings::Serializable // ^Web::Bindings::Serializable
virtual StringView interface_name() const override { return "ImageBitmap"sv; } virtual HTML::SerializeType serialize_type() const override { return HTML::SerializeType::ImageBitmap; }
virtual WebIDL::ExceptionOr<void> serialization_steps(HTML::SerializationRecord&, bool for_storage, HTML::SerializationMemory&) override; virtual WebIDL::ExceptionOr<void> serialization_steps(HTML::SerializationRecord&, bool for_storage, HTML::SerializationMemory&) override;
virtual WebIDL::ExceptionOr<void> deserialization_steps(ReadonlySpan<u32> const&, size_t& position, HTML::DeserializationMemory&) override; virtual WebIDL::ExceptionOr<void> deserialization_steps(ReadonlySpan<u32> const&, size_t& position, HTML::DeserializationMemory&) override;

View file

@ -46,7 +46,7 @@ public:
Bindings::PredefinedColorSpace color_space() const { return m_color_space; } Bindings::PredefinedColorSpace color_space() const { return m_color_space; }
virtual StringView interface_name() const override { return "ImageData"sv; } virtual HTML::SerializeType serialize_type() const override { return HTML::SerializeType::ImageData; }
virtual WebIDL::ExceptionOr<void> serialization_steps(SerializationRecord& serialized, bool for_storage, SerializationMemory&) override; virtual WebIDL::ExceptionOr<void> serialization_steps(SerializationRecord& serialized, bool for_storage, SerializationMemory&) override;
virtual WebIDL::ExceptionOr<void> deserialization_steps(ReadonlySpan<u32> const& serialized, size_t& position, DeserializationMemory&) override; virtual WebIDL::ExceptionOr<void> deserialization_steps(ReadonlySpan<u32> const& serialized, size_t& position, DeserializationMemory&) override;

View file

@ -31,10 +31,25 @@
#include <LibJS/Runtime/StringObject.h> #include <LibJS/Runtime/StringObject.h>
#include <LibJS/Runtime/TypedArray.h> #include <LibJS/Runtime/TypedArray.h>
#include <LibJS/Runtime/VM.h> #include <LibJS/Runtime/VM.h>
#include <LibWeb/Bindings/DOMExceptionPrototype.h>
#include <LibWeb/Bindings/DOMMatrixPrototype.h>
#include <LibWeb/Bindings/DOMMatrixReadOnlyPrototype.h>
#include <LibWeb/Bindings/DOMPointPrototype.h>
#include <LibWeb/Bindings/DOMPointReadOnlyPrototype.h>
#include <LibWeb/Bindings/DOMQuadPrototype.h>
#include <LibWeb/Bindings/DOMRectPrototype.h>
#include <LibWeb/Bindings/DOMRectReadOnlyPrototype.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h> #include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/FileListPrototype.h>
#include <LibWeb/Bindings/FilePrototype.h>
#include <LibWeb/Bindings/ImageBitmapPrototype.h>
#include <LibWeb/Bindings/Intrinsics.h> #include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/MessagePortPrototype.h>
#include <LibWeb/Bindings/ReadableStreamPrototype.h>
#include <LibWeb/Bindings/Serializable.h> #include <LibWeb/Bindings/Serializable.h>
#include <LibWeb/Bindings/Transferable.h> #include <LibWeb/Bindings/Transferable.h>
#include <LibWeb/Bindings/TransformStreamPrototype.h>
#include <LibWeb/Bindings/WritableStreamPrototype.h>
#include <LibWeb/Crypto/CryptoKey.h> #include <LibWeb/Crypto/CryptoKey.h>
#include <LibWeb/FileAPI/Blob.h> #include <LibWeb/FileAPI/Blob.h>
#include <LibWeb/FileAPI/File.h> #include <LibWeb/FileAPI/File.h>
@ -46,6 +61,7 @@
#include <LibWeb/Geometry/DOMQuad.h> #include <LibWeb/Geometry/DOMQuad.h>
#include <LibWeb/Geometry/DOMRect.h> #include <LibWeb/Geometry/DOMRect.h>
#include <LibWeb/Geometry/DOMRectReadOnly.h> #include <LibWeb/Geometry/DOMRectReadOnly.h>
#include <LibWeb/HTML/ImageBitmap.h>
#include <LibWeb/HTML/ImageData.h> #include <LibWeb/HTML/ImageData.h>
#include <LibWeb/HTML/MessagePort.h> #include <LibWeb/HTML/MessagePort.h>
#include <LibWeb/HTML/StructuredSerialize.h> #include <LibWeb/HTML/StructuredSerialize.h>
@ -327,7 +343,7 @@ public:
// 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);
TRY(serialize_string(m_vm, m_serialized, serializable.interface_name())); serialize_enum(m_serialized, serializable.serialize_type());
// 4. Set deep to true // 4. Set deep to true
deep = true; deep = true;
@ -960,9 +976,9 @@ public:
auto& realm = *m_vm.current_realm(); auto& realm = *m_vm.current_realm();
// 1. Let interfaceName be serialized.[[Type]]. // 1. Let interfaceName be serialized.[[Type]].
auto interface_name = TRY(deserialize_string(m_vm, m_serialized, m_position)); auto interface_name = deserialize_primitive_type<SerializeType>(m_serialized, m_position);
// 2. If the interface identified by interfaceName is not exposed in targetRealm, then throw a "DataCloneError" DOMException. // 2. If the interface identified by interfaceName is not exposed in targetRealm, then throw a "DataCloneError" DOMException.
if (!is_interface_exposed_on_target_realm(interface_name, realm)) if (!is_serializable_interface_exposed_on_target_realm(interface_name, realm))
return WebIDL::DataCloneError::create(realm, "Unsupported type"_string); return WebIDL::DataCloneError::create(realm, "Unsupported type"_string);
// 3. Set value to a new instance of the interface identified by interfaceName, created in targetRealm. // 3. Set value to a new instance of the interface identified by interfaceName, created in targetRealm.
@ -1047,44 +1063,82 @@ private:
GC::RootVector<JS::Value> m_memory; // Index -> JS value GC::RootVector<JS::Value> m_memory; // Index -> JS value
size_t m_position { 0 }; size_t m_position { 0 };
static GC::Ref<Bindings::PlatformObject> create_serialized_type(StringView interface_name, JS::Realm& realm) static bool is_serializable_interface_exposed_on_target_realm(SerializeType name, JS::Realm& realm)
{
if (interface_name == "Blob"sv)
return FileAPI::Blob::create(realm);
if (interface_name == "File"sv)
return FileAPI::File::create(realm);
if (interface_name == "FileList"sv)
return FileAPI::FileList::create(realm);
if (interface_name == "DOMException"sv)
return WebIDL::DOMException::create(realm);
if (interface_name == "DOMMatrixReadOnly"sv)
return Geometry::DOMMatrixReadOnly::create(realm);
if (interface_name == "DOMMatrix"sv)
return Geometry::DOMMatrix::create(realm);
if (interface_name == "DOMPointReadOnly"sv)
return Geometry::DOMPointReadOnly::create(realm);
if (interface_name == "DOMPoint"sv)
return Geometry::DOMPoint::create(realm);
if (interface_name == "DOMRectReadOnly"sv)
return Geometry::DOMRectReadOnly::create(realm);
if (interface_name == "DOMRect"sv)
return Geometry::DOMRect::create(realm);
if (interface_name == "CryptoKey"sv)
return Crypto::CryptoKey::create(realm);
if (interface_name == "DOMQuad"sv)
return Geometry::DOMQuad::create(realm);
if (interface_name == "ImageData"sv)
return ImageData::create(realm);
VERIFY_NOT_REACHED();
}
// FIXME: Consolidate this function with the similar is_interface_exposed_on_target_realm() used when transferring objects.
// Also, the name parameter would be better off being the interface name (as a string) so that we don't need a switch statement.
static bool is_interface_exposed_on_target_realm(StringView interface_name, JS::Realm& realm)
{ {
auto const& intrinsics = Bindings::host_defined_intrinsics(realm); auto const& intrinsics = Bindings::host_defined_intrinsics(realm);
return intrinsics.is_exposed(interface_name); switch (name) {
case SerializeType::Blob:
return intrinsics.is_interface_exposed<Bindings::BlobPrototype>(realm);
case SerializeType::File:
return intrinsics.is_interface_exposed<Bindings::FilePrototype>(realm);
case SerializeType::FileList:
return intrinsics.is_interface_exposed<Bindings::FileListPrototype>(realm);
case SerializeType::DOMException:
return intrinsics.is_interface_exposed<Bindings::DOMExceptionPrototype>(realm);
case SerializeType::DOMMatrixReadOnly:
return intrinsics.is_interface_exposed<Bindings::DOMMatrixReadOnlyPrototype>(realm);
case SerializeType::DOMMatrix:
return intrinsics.is_interface_exposed<Bindings::DOMMatrixPrototype>(realm);
case SerializeType::DOMPointReadOnly:
return intrinsics.is_interface_exposed<Bindings::DOMPointReadOnlyPrototype>(realm);
case SerializeType::DOMPoint:
return intrinsics.is_interface_exposed<Bindings::DOMPointPrototype>(realm);
case SerializeType::DOMRectReadOnly:
return intrinsics.is_interface_exposed<Bindings::DOMRectReadOnlyPrototype>(realm);
case SerializeType::DOMRect:
return intrinsics.is_interface_exposed<Bindings::DOMRectPrototype>(realm);
case SerializeType::CryptoKey:
return intrinsics.is_interface_exposed<Bindings::CryptoKeyPrototype>(realm);
case SerializeType::DOMQuad:
return intrinsics.is_interface_exposed<Bindings::DOMQuadPrototype>(realm);
case SerializeType::ImageData:
return intrinsics.is_interface_exposed<Bindings::ImageDataPrototype>(realm);
case SerializeType::ImageBitmap:
return intrinsics.is_interface_exposed<Bindings::ImageBitmapPrototype>(realm);
case SerializeType::Unknown:
dbgln("Unknown interface type for serialization: {}", to_underlying(name));
break;
default:
VERIFY_NOT_REACHED();
}
return false;
}
static GC::Ref<Bindings::PlatformObject> create_serialized_type(SerializeType serialize_type, JS::Realm& realm)
{
switch (serialize_type) {
case SerializeType::Blob:
return FileAPI::Blob::create(realm);
case SerializeType::File:
return FileAPI::File::create(realm);
case SerializeType::FileList:
return FileAPI::FileList::create(realm);
case SerializeType::DOMException:
return WebIDL::DOMException::create(realm);
case SerializeType::DOMMatrixReadOnly:
return Geometry::DOMMatrixReadOnly::create(realm);
case SerializeType::DOMMatrix:
return Geometry::DOMMatrix::create(realm);
case SerializeType::DOMPointReadOnly:
return Geometry::DOMPointReadOnly::create(realm);
case SerializeType::DOMPoint:
return Geometry::DOMPoint::create(realm);
case SerializeType::DOMRectReadOnly:
return Geometry::DOMRectReadOnly::create(realm);
case SerializeType::DOMRect:
return Geometry::DOMRect::create(realm);
case SerializeType::CryptoKey:
return Crypto::CryptoKey::create(realm);
case SerializeType::DOMQuad:
return Geometry::DOMQuad::create(realm);
case SerializeType::ImageData:
return ImageData::create(realm);
case SerializeType::ImageBitmap:
return ImageBitmap::create(realm);
case SerializeType::Unknown:
default:
VERIFY_NOT_REACHED();
}
} }
}; };
@ -1290,18 +1344,18 @@ WebIDL::ExceptionOr<SerializedTransferRecord> structured_serialize_with_transfer
return SerializedTransferRecord { .serialized = move(serialized), .transfer_data_holders = move(transfer_data_holders) }; return SerializedTransferRecord { .serialized = move(serialized), .transfer_data_holders = move(transfer_data_holders) };
} }
static bool is_interface_exposed_on_target_realm(TransferType name, JS::Realm& realm) static bool is_transferable_interface_exposed_on_target_realm(TransferType name, JS::Realm& realm)
{ {
auto const& intrinsics = Bindings::host_defined_intrinsics(realm); auto const& intrinsics = Bindings::host_defined_intrinsics(realm);
switch (name) { switch (name) {
case TransferType::MessagePort: case TransferType::MessagePort:
return intrinsics.is_exposed("MessagePort"sv); return intrinsics.is_interface_exposed<Bindings::MessagePortPrototype>(realm);
case TransferType::ReadableStream: case TransferType::ReadableStream:
return intrinsics.is_exposed("ReadableStream"sv); return intrinsics.is_interface_exposed<Bindings::ReadableStreamPrototype>(realm);
case TransferType::WritableStream: case TransferType::WritableStream:
return intrinsics.is_exposed("WritableStream"sv); return intrinsics.is_interface_exposed<Bindings::WritableStreamPrototype>(realm);
case TransferType::TransformStream: case TransferType::TransformStream:
return intrinsics.is_exposed("TransformStream"sv); return intrinsics.is_interface_exposed<Bindings::TransformStreamPrototype>(realm);
case TransferType::Unknown: case TransferType::Unknown:
dbgln("Unknown interface type for transfer: {}", to_underlying(name)); dbgln("Unknown interface type for transfer: {}", to_underlying(name));
break; break;
@ -1398,7 +1452,7 @@ WebIDL::ExceptionOr<DeserializedTransferRecord> structured_deserialize_with_tran
else { else {
// 1. Let interfaceName be transferDataHolder.[[Type]]. // 1. Let interfaceName be transferDataHolder.[[Type]].
// 2. If the interface identified by interfaceName is not exposed in targetRealm, then throw a "DataCloneError" DOMException. // 2. If the interface identified by interfaceName is not exposed in targetRealm, then throw a "DataCloneError" DOMException.
if (!is_interface_exposed_on_target_realm(type, target_realm)) if (!is_transferable_interface_exposed_on_target_realm(type, target_realm))
return WebIDL::DataCloneError::create(target_realm, "Unknown type transferred"_string); return WebIDL::DataCloneError::create(target_realm, "Unknown type transferred"_string);
// 3. Set value to a new instance of the interface identified by interfaceName, created in targetRealm. // 3. Set value to a new instance of the interface identified by interfaceName, created in targetRealm.

View file

@ -44,16 +44,6 @@ struct DeserializedRecord {
size_t position; size_t position;
}; };
enum class TransferType : u8 {
Unknown = 0,
MessagePort = 1,
ArrayBuffer = 2,
ResizableArrayBuffer = 3,
ReadableStream = 4,
WritableStream = 5,
TransformStream = 6,
};
WebIDL::ExceptionOr<SerializationRecord> structured_serialize(JS::VM& vm, 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_for_storage(JS::VM& vm, JS::Value);
WebIDL::ExceptionOr<SerializationRecord> structured_serialize_internal(JS::VM& vm, JS::Value, bool for_storage, SerializationMemory&); WebIDL::ExceptionOr<SerializationRecord> structured_serialize_internal(JS::VM& vm, JS::Value, bool for_storage, SerializationMemory&);

View file

@ -17,4 +17,32 @@ using DeserializationMemory = GC::RootVector<JS::Value>;
using SerializationRecord = Vector<u32>; using SerializationRecord = Vector<u32>;
using SerializationMemory = HashMap<GC::Root<JS::Value>, u32>; using SerializationMemory = HashMap<GC::Root<JS::Value>, u32>;
enum class SerializeType : u8 {
Unknown = 0,
DOMException = 1,
DOMRectReadOnly = 2,
DOMRect = 3,
Blob = 4,
ImageBitmap = 5,
CryptoKey = 6,
File = 7,
FileList = 8,
DOMMatrixReadOnly = 9,
DOMMatrix = 10,
DOMPointReadOnly = 11,
DOMPoint = 12,
DOMQuad = 13,
ImageData = 14,
};
enum class TransferType : u8 {
Unknown = 0,
MessagePort = 1,
ArrayBuffer = 2,
ResizableArrayBuffer = 3,
ReadableStream = 4,
WritableStream = 5,
TransformStream = 6,
};
} }

View file

@ -111,7 +111,7 @@ public:
FlyString const& message() const { return m_message; } FlyString const& message() const { return m_message; }
u16 code() const { return get_legacy_code_for_name(m_name); } u16 code() const { return get_legacy_code_for_name(m_name); }
virtual StringView interface_name() const override { return "DOMException"sv; } virtual HTML::SerializeType serialize_type() const override { return HTML::SerializeType::DOMException; }
virtual ExceptionOr<void> serialization_steps(HTML::SerializationRecord& record, bool for_storage, HTML::SerializationMemory&) override; virtual ExceptionOr<void> serialization_steps(HTML::SerializationRecord& record, bool for_storage, HTML::SerializationMemory&) override;
virtual ExceptionOr<void> deserialization_steps(ReadonlySpan<u32> const& record, size_t& position, HTML::DeserializationMemory&) override; virtual ExceptionOr<void> deserialization_steps(ReadonlySpan<u32> const& record, size_t& position, HTML::DeserializationMemory&) override;

View file

@ -76,7 +76,11 @@ static ErrorOr<void> generate_intrinsic_definitions(StringView output_path, Inte
generator.append(R"~~~( generator.append(R"~~~(
#include <LibGC/DeferGC.h> #include <LibGC/DeferGC.h>
#include <LibJS/Runtime/Object.h> #include <LibJS/Runtime/Object.h>
#include <LibWeb/Bindings/Intrinsics.h>)~~~"); #include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/HTML/DedicatedWorkerGlobalScope.h>
#include <LibWeb/HTML/SharedWorkerGlobalScope.h>
#include <LibWeb/HTML/ShadowRealmGlobalScope.h>)~~~");
for (auto& interface : interface_sets.intrinsics) { for (auto& interface : interface_sets.intrinsics) {
auto gen = generator.fork(); auto gen = generator.fork();
@ -134,11 +138,77 @@ void Intrinsics::create_web_namespace<@namespace_class@>(JS::Realm& realm)
)~~~"); )~~~");
}; };
auto add_interface = [](SourceGenerator& gen, StringView name, StringView prototype_class, StringView constructor_class, Optional<LegacyConstructor> const& legacy_constructor, StringView named_properties_class) { auto add_interface = [](SourceGenerator& gen, InterfaceSets const& interface_sets, StringView name, StringView prototype_class, StringView constructor_class, Optional<LegacyConstructor> const& legacy_constructor, StringView named_properties_class) {
gen.set("interface_name", name); gen.set("interface_name", name);
gen.set("prototype_class", prototype_class); gen.set("prototype_class", prototype_class);
gen.set("constructor_class", constructor_class); gen.set("constructor_class", constructor_class);
// https://webidl.spec.whatwg.org/#dfn-exposed
// An interface, callback interface, namespace, or member construct is exposed in a given realm realm if the
// following steps return true:
// FIXME: Make this compatible with the non-interface types.
gen.append(R"~~~(
template<>
bool Intrinsics::is_interface_exposed<@prototype_class@>(JS::Realm& realm) const
{
[[maybe_unused]] auto& global_object = realm.global_object();
)~~~");
// 1. If constructs exposure set is not *, and realm.[[GlobalObject]] does not implement an interface that is in constructs exposure set, then return false.
auto window_exposed_iterator = interface_sets.window_exposed.find_if([&name](IDL::Interface const& interface) {
return interface.name == name;
});
if (window_exposed_iterator != interface_sets.window_exposed.end()) {
gen.append(R"~~~(
if (is<HTML::Window>(global_object))
return true;
)~~~");
}
auto dedicated_worker_exposed_iterator = interface_sets.dedicated_worker_exposed.find_if([&name](IDL::Interface const& interface) {
return interface.name == name;
});
if (dedicated_worker_exposed_iterator != interface_sets.dedicated_worker_exposed.end()) {
gen.append(R"~~~(
if (is<HTML::DedicatedWorkerGlobalScope>(global_object))
return true;
)~~~");
}
auto shared_worker_exposed_iterator = interface_sets.shared_worker_exposed.find_if([&name](IDL::Interface const& interface) {
return interface.name == name;
});
if (shared_worker_exposed_iterator != interface_sets.shared_worker_exposed.end()) {
gen.append(R"~~~(
if (is<HTML::SharedWorkerGlobalScope>(global_object))
return true;
)~~~");
}
auto shadow_realm_exposed_iterator = interface_sets.shadow_realm_exposed.find_if([&name](IDL::Interface const& interface) {
return interface.name == name;
});
if (shadow_realm_exposed_iterator != interface_sets.shadow_realm_exposed.end()) {
gen.append(R"~~~(
if (is<HTML::ShadowRealmGlobalScope>(global_object))
return true;
)~~~");
}
// FIXME: 2. If realms settings object is not a secure context, and construct is conditionally exposed on
// [SecureContext], then return false.
// FIXME: 3. If realms settings objects cross-origin isolated capability is false, and construct is
// conditionally exposed on [CrossOriginIsolated], then return false.
gen.append(R"~~~(
return false;
}
)~~~");
gen.append(R"~~~( gen.append(R"~~~(
template<> template<>
void Intrinsics::create_web_prototype_and_constructor<@prototype_class@>(JS::Realm& realm) void Intrinsics::create_web_prototype_and_constructor<@prototype_class@>(JS::Realm& realm)
@ -188,7 +258,7 @@ void Intrinsics::create_web_prototype_and_constructor<@prototype_class@>(JS::Rea
if (interface.is_namespace) if (interface.is_namespace)
add_namespace(gen, interface.name, interface.namespace_class); add_namespace(gen, interface.name, interface.namespace_class);
else else
add_interface(gen, interface.namespaced_name, interface.prototype_class, interface.constructor_class, lookup_legacy_constructor(interface), named_properties_class); add_interface(gen, interface_sets, interface.namespaced_name, interface.prototype_class, interface.constructor_class, lookup_legacy_constructor(interface), named_properties_class);
} }
generator.append(R"~~~( generator.append(R"~~~(

View file

@ -41,9 +41,6 @@ Text/input/wpt-import/html/syntax/parsing/html5lib_tests16.html
Text/input/wpt-import/html/syntax/parsing/html5lib_tests19.html Text/input/wpt-import/html/syntax/parsing/html5lib_tests19.html
Text/input/wpt-import/html/syntax/parsing/html5lib_tests5.html Text/input/wpt-import/html/syntax/parsing/html5lib_tests5.html
; Unknown, imported as skipped in #2148
Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/structuredclone_0.html
; Flaky, apparently due to font loading ; Flaky, apparently due to font loading
Text/input/wpt-import/css/css-flexbox/flex-item-compressible-001.html Text/input/wpt-import/css/css-flexbox/flex-item-compressible-001.html

View file

@ -0,0 +1,20 @@
iframe received an object: Error
iframe received an object: [object DOMRectReadOnly]
iframe received an object: [object DOMRect]
iframe received an object: [object Blob]
iframe received an object: [object ImageBitmap]
iframe received an object: [object CryptoKey]
iframe received an object: [object File]
iframe received an object: [object FileList]
iframe received an object: matrix(1, 2, 3, 4, 5, 6)
iframe received an object: matrix(6, 5, 4, 3, 2, 1)
iframe received an object: [object DOMPointReadOnly]
iframe received an object: [object DOMPoint]
iframe received an object: [object DOMQuad]
iframe received an object: [object ImageData]
iframe received an object: [object MessagePort]
iframe received an object: [object ArrayBuffer]
iframe received an object: [object ArrayBuffer]
iframe received an object: [object ReadableStream]
iframe received an object: [object WritableStream]
iframe received an object: [object TransformStream]

View file

@ -1,15 +1,9 @@
Summary
Harness status: OK Harness status: OK
Rerun
Found 41 tests Found 41 tests
36 Pass 41 Pass
5 Fail Pass Primitive string is cloned
Details
Result Test Name MessagePass Primitive string is cloned
Pass Primitive integer is cloned Pass Primitive integer is cloned
Pass Primitive floating point is cloned Pass Primitive floating point is cloned
Pass Primitive floating point (negative) is cloned Pass Primitive floating point (negative) is cloned
@ -27,13 +21,13 @@ Pass Object properties are cloned
Pass Prototype chains are not walked. Pass Prototype chains are not walked.
Pass Property descriptors of Objects are not cloned Pass Property descriptors of Objects are not cloned
Pass Cycles are preserved in Objects Pass Cycles are preserved in Objects
Fail Identity of duplicates is preserved Pass Identity of duplicates is preserved
Pass Property order is preserved Pass Property order is preserved
Pass Enumerable properties of Arrays are cloned Pass Enumerable properties of Arrays are cloned
Pass Property descriptors of Arrays are not cloned Pass Property descriptors of Arrays are not cloned
Pass Cycles are preserved in Arrays Pass Cycles are preserved in Arrays
Fail ImageData object can be cloned Cannot serialize platform objects Pass ImageData object can be cloned
Fail ImageData expandos are not cloned Cannot serialize platform objects Pass ImageData expandos are not cloned
Pass Window objects cannot be cloned Pass Window objects cannot be cloned
Pass Document objects cannot be cloned Pass Document objects cannot be cloned
Pass Empty Error objects can be cloned Pass Empty Error objects can be cloned
@ -48,5 +42,5 @@ Pass URIError objects from other realms are treated as URIError
Pass Cloning a modified Error Pass Cloning a modified Error
Pass Error.message: getter is ignored when cloning Pass Error.message: getter is ignored when cloning
Pass Error.message: undefined property is stringified Pass Error.message: undefined property is stringified
Fail DOMException objects can be cloned Cannot serialize platform objects Pass DOMException objects can be cloned
Fail DOMException objects created by the UA can be cloned Cannot serialize platform objects Pass DOMException objects created by the UA can be cloned

View file

@ -0,0 +1,77 @@
<!DOCTYPE html>
<iframe id="testframe"></iframe>
<script src="include.js"></script>
<script>
asyncTest((done) => {
const testframe = document.getElementById("testframe");
const canvas = document.createElement("canvas");
window.onmessage = messageEvent => {
if (messageEvent.data === "done") {
done();
return;
}
println(messageEvent.data);
};
testframe.onload = () => {
canvas.toBlob(async (canvasBlob) => {
const imageBitmap = await createImageBitmap(canvasBlob);
const aesGcm128bitKey = await crypto.subtle.generateKey({ name: "AES-GCM", length: 128 }, true, ["encrypt"]);
const input = document.createElement("input");
input.type = "file";
const serializableTypes = [
new DOMException(),
new DOMRectReadOnly(),
new DOMRect(),
new Blob([""], { type: "text/plain" }),
imageBitmap,
aesGcm128bitKey,
new File([""], "test.txt"),
input.files,
new DOMMatrixReadOnly([1, 2, 3, 4, 5, 6]),
new DOMMatrix([6, 5, 4, 3, 2, 1]),
new DOMPointReadOnly(),
new DOMPoint(),
new DOMQuad(),
new ImageData(1, 1),
];
const messageChannel = new MessageChannel();
const transferableTypes = [
messageChannel.port2,
new ArrayBuffer(1),
new ArrayBuffer(1, { maxByteLength: 2 }),
new ReadableStream(),
new WritableStream(),
new TransformStream(),
];
for (const serializableType of serializableTypes) {
testframe.contentWindow.postMessage(serializableType);
}
for (const transferableType of transferableTypes) {
testframe.contentWindow.postMessage(transferableType, { transfer: [ transferableType ] });
}
testframe.contentWindow.postMessage("done");
});
};
testframe.srcdoc = `
\u003cscript\u003e
window.onmessage = messageEvent => {
if (messageEvent.data === "done") {
window.parent.postMessage("done");
return;
}
window.parent.postMessage("iframe received an object: " + messageEvent.data);
};
\u003c/script\u003e`
});
</script>