diff --git a/Libraries/LibWeb/HTML/StructuredSerialize.cpp b/Libraries/LibWeb/HTML/StructuredSerialize.cpp
index a46d37c8647..8ee29134f9f 100644
--- a/Libraries/LibWeb/HTML/StructuredSerialize.cpp
+++ b/Libraries/LibWeb/HTML/StructuredSerialize.cpp
@@ -16,6 +16,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -153,6 +154,7 @@ public:
Serializer(JS::VM& vm, SerializationMemory& memory, bool for_storage)
: m_vm(vm)
, m_memory(memory)
+ , m_next_id(memory.size())
, m_for_storage(for_storage)
{
}
@@ -635,14 +637,16 @@ WebIDL::ExceptionOr serialize_viewed_array_buffer(JS::VM& vm, Vector&
}
// 2. Let buffer be the value of value's [[ViewedArrayBuffer]] internal slot.
- auto* buffer = view.viewed_array_buffer();
+ JS::Value buffer = view.viewed_array_buffer();
// 3. Let bufferSerialized be ? StructuredSerializeInternal(buffer, forStorage, memory).
- auto buffer_serialized = TRY(structured_serialize_internal(vm, JS::Value(buffer), for_storage, memory));
+ auto buffer_serialized = TRY(structured_serialize_internal(vm, buffer, for_storage, memory));
// 4. Assert: bufferSerialized.[[Type]] is "ArrayBuffer", "ResizableArrayBuffer", "SharedArrayBuffer", or "GrowableSharedArrayBuffer".
// NOTE: We currently only implement this for ArrayBuffer
- VERIFY(buffer_serialized[0] == ValueTag::ArrayBuffer);
+ // NOTE: Object reference + memory check is required when ArrayBuffer is transfered.
+ auto tag = buffer_serialized[0];
+ VERIFY(tag == ValueTag::ArrayBuffer || (tag == ValueTag::ObjectReference && memory.contains(buffer)));
// 5. If value has a [[DataView]] internal slot, then set serialized to { [[Type]]: "ArrayBufferView", [[Constructor]]: "DataView",
// [[ArrayBufferSerialized]]: bufferSerialized, [[ByteLength]]: value.[[ByteLength]], [[ByteOffset]]: value.[[ByteOffset]] }.
@@ -1114,14 +1118,18 @@ WebIDL::ExceptionOr structured_serialize_with_transfer
// 2. For each transferable of transferList:
for (auto const& transferable : transfer_list) {
+ auto is_array_buffer = is(*transferable);
// 1. If transferable has neither an [[ArrayBufferData]] internal slot nor a [[Detached]] internal slot, then throw a "DataCloneError" DOMException.
- // FIXME: Handle transferring ArrayBufferData objects
- if (!is(*transferable)) {
+ // FIXME: Handle transferring objects with [[Detached]] internal slot.
+ if (!is_array_buffer && !is(*transferable)) {
return WebIDL::DataCloneError::create(*vm.current_realm(), "Cannot transfer type"_string);
}
- // FIXME: 2. If transferable has an [[ArrayBufferData]] internal slot and IsSharedArrayBuffer(transferable) is true, then throw a "DataCloneError" DOMException.
+ // 2. If transferable has an [[ArrayBufferData]] internal slot and IsSharedArrayBuffer(transferable) is true, then throw a "DataCloneError" DOMException.
+ if (is_array_buffer && dynamic_cast(*transferable).is_shared_array_buffer()) {
+ return WebIDL::DataCloneError::create(*vm.current_realm(), "Cannot transfer shared array buffer"_string);
+ }
// 3. If memory[transferable] exists, then throw a "DataCloneError" DOMException.
auto transferable_value = JS::Value(transferable);
@@ -1130,7 +1138,7 @@ WebIDL::ExceptionOr structured_serialize_with_transfer
}
// 4. Set memory[transferable] to { [[Type]]: an uninitialized value }.
- memory.set(GC::make_root(transferable_value), NumericLimits::max());
+ memory.set(GC::make_root(transferable_value), memory.size());
}
// 3. Let serialized be ? StructuredSerializeInternal(value, false, memory).
@@ -1142,7 +1150,13 @@ WebIDL::ExceptionOr structured_serialize_with_transfer
// 5. For each transferable of transferList:
for (auto& transferable : transfer_list) {
- // 1. FIXME: If transferable has an [[ArrayBufferData]] internal slot and IsDetachedBuffer(transferable) is true, then throw a "DataCloneError" DOMException.
+ auto is_array_buffer = is(*transferable);
+ auto is_detached = is_array_buffer && dynamic_cast(*transferable).is_detached();
+
+ // 1. If transferable has an [[ArrayBufferData]] internal slot and IsDetachedBuffer(transferable) is true, then throw a "DataCloneError" DOMException.
+ if (is_detached) {
+ return WebIDL::DataCloneError::create(*vm.current_realm(), "Cannot transfer detached buffer"_string);
+ }
// 2. If transferable has a [[Detached]] internal slot and transferable.[[Detached]] is true, then throw a "DataCloneError" DOMException.
if (is(*transferable)) {
@@ -1156,8 +1170,32 @@ WebIDL::ExceptionOr structured_serialize_with_transfer
// IMPLEMENTATION DEFINED: We just create a data holder here, our memory holds indices into the SerializationRecord
TransferDataHolder data_holder;
- // FIXME 4. If transferable has an [[ArrayBufferData]] internal slot, then:
- if (false) {
+ // 4. If transferable has an [[ArrayBufferData]] internal slot, then:
+ if (is_array_buffer) {
+ // 1. If transferable has an [[ArrayBufferMaxByteLength]] internal slot, then:
+ auto& array_buffer = dynamic_cast(*transferable);
+ if (!array_buffer.is_fixed_length()) {
+ // 1. Set dataHolder.[[Type]] to "ResizableArrayBuffer".
+ // 2. Set dataHolder.[[ArrayBufferData]] to transferable.[[ArrayBufferData]].
+ // 3. Set dataHolder.[[ArrayBufferByteLength]] to transferable.[[ArrayBufferByteLength]].
+ // 4. Set dataHolder.[[ArrayBufferMaxByteLength]] to transferable.[[ArrayBufferMaxByteLength]].
+ serialize_enum(data_holder.data, TransferType::ResizableArrayBuffer);
+ MUST(serialize_bytes(vm, data_holder.data, array_buffer.buffer().bytes())); // serializes both byte length and bytes
+ serialize_primitive_type(data_holder.data, array_buffer.max_byte_length());
+ }
+
+ // 2. Otherwise:
+ else {
+ // 1. Set dataHolder.[[Type]] to "ArrayBuffer".
+ // 2. Set dataHolder.[[ArrayBufferData]] to transferable.[[ArrayBufferData]].
+ // 3. Set dataHolder.[[ArrayBufferByteLength]] to transferable.[[ArrayBufferByteLength]].
+ serialize_enum(data_holder.data, TransferType::ArrayBuffer);
+ MUST(serialize_bytes(vm, data_holder.data, array_buffer.buffer().bytes())); // serializes both byte length and bytes
+ }
+
+ // 3. Perform ? DetachArrayBuffer(transferable).
+ // NOTE: Specifications can use the [[ArrayBufferDetachKey]] internal slot to prevent ArrayBuffers from being detached. This is used in WebAssembly JavaScript Interface, for example. See: https://html.spec.whatwg.org/multipage/references.html#refsWASMJS
+ TRY(JS::detach_array_buffer(vm, array_buffer));
}
// 5. Otherwise:
@@ -1170,7 +1208,7 @@ WebIDL::ExceptionOr structured_serialize_with_transfer
auto interface_name = transferable_object.primary_interface();
// 3. Set dataHolder.[[Type]] to interfaceName.
- data_holder.data.append(to_underlying(interface_name));
+ serialize_enum(data_holder.data, interface_name);
// 4. Perform the appropriate transfer steps for the interface identified by interfaceName, given transferable and dataHolder.
TRY(transferable_object.transfer_steps(data_holder));
@@ -1187,15 +1225,15 @@ WebIDL::ExceptionOr structured_serialize_with_transfer
return SerializedTransferRecord { .serialized = move(serialized), .transfer_data_holders = move(transfer_data_holders) };
}
-static bool is_interface_exposed_on_target_realm(u8 name, JS::Realm& realm)
+static bool is_interface_exposed_on_target_realm(TransferType name, JS::Realm& realm)
{
auto const& intrinsics = Bindings::host_defined_intrinsics(realm);
- switch (static_cast(name)) {
+ switch (name) {
case TransferType::MessagePort:
return intrinsics.is_exposed("MessagePort"sv);
break;
default:
- dbgln("Unknown interface type for transfer: {}", name);
+ dbgln("Unknown interface type for transfer: {}", to_underlying(name));
break;
}
return false;
@@ -1209,6 +1247,10 @@ static WebIDL::ExceptionOr> create_transferred
TRY(message_port->transfer_receiving_steps(transfer_data_holder));
return message_port;
}
+ case TransferType::ArrayBuffer:
+ case TransferType::ResizableArrayBuffer:
+ dbgln("ArrayBuffer ({}) is not a platform object.", to_underlying(name));
+ break;
}
VERIFY_NOT_REACHED();
}
@@ -1226,39 +1268,54 @@ WebIDL::ExceptionOr structured_deserialize_with_tran
// 3. For each transferDataHolder of serializeWithTransferResult.[[TransferDataHolders]]:
for (auto& transfer_data_holder : serialize_with_transfer_result.transfer_data_holders) {
+ if (transfer_data_holder.data.is_empty())
+ continue;
+
// 1. Let value be an uninitialized value.
JS::Value value;
- // FIXME: 2. If transferDataHolder.[[Type]] is "ArrayBuffer", then set value to a new ArrayBuffer object in targetRealm
+ size_t data_holder_position = 0;
+ auto type = deserialize_primitive_type(transfer_data_holder.data.span(), data_holder_position);
+
+ // 2. If transferDataHolder.[[Type]] is "ArrayBuffer", then set value to a new ArrayBuffer object in targetRealm
// whose [[ArrayBufferData]] internal slot value is transferDataHolder.[[ArrayBufferData]], and
// whose [[ArrayBufferByteLength]] internal slot value is transferDataHolder.[[ArrayBufferByteLength]].
// NOTE: In cases where the original memory occupied by [[ArrayBufferData]] is accessible during the deserialization,
// this step is unlikely to throw an exception, as no new memory needs to be allocated: the memory occupied by
// [[ArrayBufferData]] is instead just getting transferred into the new ArrayBuffer. This could be true, for example,
// when both the source and target realms are in the same process.
- if (false) {
+ if (type == TransferType::ArrayBuffer) {
+ auto bytes = TRY(deserialize_bytes(vm, transfer_data_holder.data, data_holder_position));
+ JS::ArrayBuffer* data = TRY(JS::allocate_array_buffer(vm, target_realm.intrinsics().array_buffer_constructor(), bytes.size()));
+ bytes.span().copy_to(data->buffer().span());
+ value = JS::Value(data);
}
- // FIXME: 3. Otherwise, if transferDataHolder.[[Type]] is "ResizableArrayBuffer", then set value to a new ArrayBuffer object
+ // 3. Otherwise, if transferDataHolder.[[Type]] is "ResizableArrayBuffer", then set value to a new ArrayBuffer object
// in targetRealm whose [[ArrayBufferData]] internal slot value is transferDataHolder.[[ArrayBufferData]], whose
// [[ArrayBufferByteLength]] internal slot value is transferDataHolder.[[ArrayBufferByteLength]], and whose
// [[ArrayBufferMaxByteLength]] internal slot value is transferDataHolder.[[ArrayBufferMaxByteLength]].
// NOTE: For the same reason as the previous step, this step is also unlikely to throw an exception.
- else if (false) {
+ else if (type == TransferType::ResizableArrayBuffer) {
+ auto bytes = TRY(deserialize_bytes(vm, transfer_data_holder.data, data_holder_position));
+ auto max_byte_length = deserialize_primitive_type(transfer_data_holder.data, data_holder_position);
+ JS::ArrayBuffer* data = TRY(JS::allocate_array_buffer(vm, target_realm.intrinsics().array_buffer_constructor(), bytes.size()));
+ data->set_max_byte_length(max_byte_length);
+ bytes.span().copy_to(data->buffer().span());
+ value = JS::Value(data);
}
// 4. Otherwise:
else {
// 1. Let interfaceName be transferDataHolder.[[Type]].
- u8 const interface_name = transfer_data_holder.data.take_first();
-
// 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, target_realm))
+ if (!is_interface_exposed_on_target_realm(type, target_realm))
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.
// 4. Perform the appropriate transfer-receiving steps for the interface identified by interfaceName given transferDataHolder and value.
- value = TRY(create_transferred_value(static_cast(interface_name), target_realm, transfer_data_holder));
+ transfer_data_holder.data.remove(0, data_holder_position);
+ value = TRY(create_transferred_value(type, target_realm, transfer_data_holder));
}
// 5. Set memory[transferDataHolder] to value.
@@ -1351,7 +1408,7 @@ ErrorOr encode(Encoder& encoder, ::Web::HTML::SerializedTransferRecord con
template<>
ErrorOr<::Web::HTML::TransferDataHolder> decode(Decoder& decoder)
{
- auto data = TRY(decoder.decode>());
+ auto data = TRY(decoder.decode>());
auto fds = TRY(decoder.decode>());
return ::Web::HTML::TransferDataHolder { move(data), move(fds) };
}
diff --git a/Libraries/LibWeb/HTML/StructuredSerialize.h b/Libraries/LibWeb/HTML/StructuredSerialize.h
index 0a3e195ba85..981b6210e8e 100644
--- a/Libraries/LibWeb/HTML/StructuredSerialize.h
+++ b/Libraries/LibWeb/HTML/StructuredSerialize.h
@@ -25,7 +25,7 @@
namespace Web::HTML {
struct TransferDataHolder {
- Vector data;
+ Vector data;
Vector fds;
};
@@ -46,6 +46,8 @@ struct DeserializedRecord {
enum class TransferType : u8 {
MessagePort,
+ ArrayBuffer,
+ ResizableArrayBuffer,
};
WebIDL::ExceptionOr structured_serialize(JS::VM& vm, JS::Value);
diff --git a/Tests/LibWeb/Text/expected/Worker/Worker-postMessage-transfer.txt b/Tests/LibWeb/Text/expected/Worker/Worker-postMessage-transfer.txt
index 7db7e90e53f..ceaa5eb28fb 100644
--- a/Tests/LibWeb/Text/expected/Worker/Worker-postMessage-transfer.txt
+++ b/Tests/LibWeb/Text/expected/Worker/Worker-postMessage-transfer.txt
@@ -1 +1,3 @@
+Buffer length before transfer: 12
+Buffer length after transfer: 0
Message received from worker: Hello, world
diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.txt b/Tests/LibWeb/Text/expected/wpt-import/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.txt
index 3a339e4d727..996071f6794 100644
--- a/Tests/LibWeb/Text/expected/wpt-import/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.txt
+++ b/Tests/LibWeb/Text/expected/wpt-import/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.txt
@@ -2,8 +2,8 @@ Harness status: OK
Found 150 tests
-124 Pass
-25 Fail
+133 Pass
+16 Fail
1 Optional Feature Unsupported
Pass primitive undefined
Pass primitive null
@@ -118,10 +118,10 @@ Fail Object ImageData object, ImageData 1x1 non-transparent non-black
Pass Array sparse
Pass Array with non-index property
Pass Object with index property and length
-Fail Array with circular reference
-Fail Object with circular reference
-Fail Array with identical property values
-Fail Object with identical property values
+Pass Array with circular reference
+Pass Object with circular reference
+Pass Array with identical property values
+Pass Object with identical property values
Pass Object with property on prototype
Pass Object with non-enumerable property
Pass Object with non-writable property
@@ -143,14 +143,14 @@ Pass Length-tracking TypedArray
Pass Length-tracking DataView
Pass Serializing OOB TypedArray throws
Pass Serializing OOB DataView throws
-Fail ArrayBuffer
-Fail MessagePort
-Fail A detached ArrayBuffer cannot be transferred
+Pass ArrayBuffer
+Pass MessagePort
+Pass A detached ArrayBuffer cannot be transferred
Pass A detached platform object cannot be transferred
Pass Transferring a non-transferable platform object fails
-Fail An object whose interface is deleted from the global object must still be received
+Pass An object whose interface is deleted from the global object must still be received
Optional Feature Unsupported A subclass instance will be received as its closest transferable superclass
-Fail Resizable ArrayBuffer is transferable
+Pass Resizable ArrayBuffer is transferable
Fail Length-tracking TypedArray is transferable
Fail Length-tracking DataView is transferable
Pass Transferring OOB TypedArray throws
diff --git a/Tests/LibWeb/Text/input/Worker/Worker-postMessage-transfer.html b/Tests/LibWeb/Text/input/Worker/Worker-postMessage-transfer.html
index 7d8b1e9227e..a514e6afaca 100644
--- a/Tests/LibWeb/Text/input/Worker/Worker-postMessage-transfer.html
+++ b/Tests/LibWeb/Text/input/Worker/Worker-postMessage-transfer.html
@@ -24,6 +24,8 @@
const message = encoder.encode("Hello, world");
const myBuf = message.buffer;
+ println('Buffer length before transfer: ' + myBuf.byteLength);
worker.postMessage(myBuf, [myBuf]);
+ println('Buffer length after transfer: ' + myBuf.byteLength);
});