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); });