diff --git a/cli/tests/unit/message_channel_test.ts b/cli/tests/unit/message_channel_test.ts index 0cb2671d5a..2c60626581 100644 --- a/cli/tests/unit/message_channel_test.ts +++ b/cli/tests/unit/message_channel_test.ts @@ -31,3 +31,29 @@ Deno.test("messagechannel", async () => { mc.port2.close(); mc2.port2.close(); }); + +Deno.test("messagechannel clone port", async () => { + const mc = new MessageChannel(); + const mc2 = new MessageChannel(); + assert(mc.port1); + assert(mc.port2); + + const promise = deferred(); + + mc.port2.onmessage = (e) => { + const { port } = e.data; + assertEquals(e.ports.length, 1); + assert(e.ports[0] instanceof MessagePort); + assertEquals(e.ports[0], port); + e.ports[0].close(); + promise.resolve(); + }; + + mc.port1.postMessage({ port: mc2.port1 }, [mc2.port1]); + mc.port1.close(); + + await promise; + + mc.port2.close(); + mc2.port2.close(); +}); diff --git a/core/bindings.rs b/core/bindings.rs index a0a3a1cc46..c96a8559ca 100644 --- a/core/bindings.rs +++ b/core/bindings.rs @@ -11,6 +11,7 @@ use crate::PromiseId; use crate::ZeroCopyBuf; use log::debug; use rusty_v8 as v8; +use serde::Deserialize; use serde::Serialize; use serde_v8::to_v8; use std::convert::TryFrom; @@ -34,6 +35,9 @@ lazy_static::lazy_static! { v8::ExternalReference { function: queue_microtask.map_fn_to() }, + v8::ExternalReference { + function: create_host_object.map_fn_to() + }, v8::ExternalReference { function: encode.map_fn_to() }, @@ -130,6 +134,7 @@ pub fn initialize_context<'s>( set_func(scope, core_val, "getPromiseDetails", get_promise_details); set_func(scope, core_val, "getProxyDetails", get_proxy_details); set_func(scope, core_val, "memoryUsage", memory_usage); + set_func(scope, core_val, "createHostObject", create_host_object); // Direct bindings on `window`. set_func(scope, global, "queueMicrotask", queue_microtask); @@ -514,9 +519,11 @@ fn decode( }; } -struct SerializeDeserialize {} +struct SerializeDeserialize<'a> { + host_objects: Option>, +} -impl v8::ValueSerializerImpl for SerializeDeserialize { +impl<'a> v8::ValueSerializerImpl for SerializeDeserialize<'a> { #[allow(unused_variables)] fn throw_data_clone_error<'s>( &mut self, @@ -526,30 +533,104 @@ impl v8::ValueSerializerImpl for SerializeDeserialize { let error = v8::Exception::error(scope, message); scope.throw_exception(error); } + + fn write_host_object<'s>( + &mut self, + scope: &mut v8::HandleScope<'s>, + object: v8::Local<'s, v8::Object>, + value_serializer: &mut dyn v8::ValueSerializerHelper, + ) -> Option { + if let Some(host_objects) = self.host_objects { + for i in 0..host_objects.length() { + let value = host_objects.get_index(scope, i).unwrap(); + if value == object { + value_serializer.write_uint32(i); + return Some(true); + } + } + } + let message = v8::String::new(scope, "Unsupported object type").unwrap(); + self.throw_data_clone_error(scope, message); + None + } } -impl v8::ValueDeserializerImpl for SerializeDeserialize {} +impl<'a> v8::ValueDeserializerImpl for SerializeDeserialize<'a> { + fn read_host_object<'s>( + &mut self, + scope: &mut v8::HandleScope<'s>, + value_deserializer: &mut dyn v8::ValueDeserializerHelper, + ) -> Option> { + if let Some(host_objects) = self.host_objects { + let mut i = 0; + if !value_deserializer.read_uint32(&mut i) { + return None; + } + let maybe_value = host_objects.get_index(scope, i); + if let Some(value) = maybe_value { + return value.to_object(scope); + } + } + + let message = + v8::String::new(scope, "Failed to deserialize host object").unwrap(); + let error = v8::Exception::error(scope, message); + scope.throw_exception(error); + None + } +} fn serialize( scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, mut rv: v8::ReturnValue, ) { - let serialize_deserialize = Box::new(SerializeDeserialize {}); + let value = args.get(0); + + let options: Option = + match serde_v8::from_v8(scope, args.get(1)) { + Ok(opts) => opts, + Err(_) => { + throw_type_error(scope, "Invalid argument 2"); + return; + } + }; + + let options = + options.unwrap_or(SerializeDeserializeOptions { host_objects: None }); + + let host_objects = match options.host_objects { + Some(value) => match v8::Local::::try_from(value.v8_value) { + Ok(host_objects) => Some(host_objects), + Err(_) => { + throw_type_error(scope, "host_objects not an array"); + return; + } + }, + None => None, + }; + + let serialize_deserialize = Box::new(SerializeDeserialize { host_objects }); let mut value_serializer = v8::ValueSerializer::new(scope, serialize_deserialize); - match value_serializer.write_value(scope.get_current_context(), args.get(0)) { + match value_serializer.write_value(scope.get_current_context(), value) { Some(true) => { let vector = value_serializer.release(); let zbuf: ZeroCopyBuf = vector.into(); rv.set(to_v8(scope, zbuf).unwrap()); } _ => { - throw_type_error(scope, "Invalid argument"); + throw_type_error(scope, "Failed to serialize response"); } } } +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct SerializeDeserializeOptions<'a> { + host_objects: Option>, +} + fn deserialize( scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, @@ -558,15 +639,37 @@ fn deserialize( let zero_copy: ZeroCopyBuf = match serde_v8::from_v8(scope, args.get(0)) { Ok(zbuf) => zbuf, Err(_) => { - throw_type_error(scope, "Invalid argument"); + throw_type_error(scope, "Invalid argument 1"); return; } }; - let buf = &zero_copy; - let serialize_deserialize = Box::new(SerializeDeserialize {}); + let options: Option = + match serde_v8::from_v8(scope, args.get(1)) { + Ok(opts) => opts, + Err(_) => { + throw_type_error(scope, "Invalid argument 2"); + return; + } + }; + + let options = + options.unwrap_or(SerializeDeserializeOptions { host_objects: None }); + + let host_objects = match options.host_objects { + Some(value) => match v8::Local::::try_from(value.v8_value) { + Ok(host_objects) => Some(host_objects), + Err(_) => { + throw_type_error(scope, "host_objects not an array"); + return; + } + }, + None => None, + }; + + let serialize_deserialize = Box::new(SerializeDeserialize { host_objects }); let mut value_deserializer = - v8::ValueDeserializer::new(scope, serialize_deserialize, buf); + v8::ValueDeserializer::new(scope, serialize_deserialize, &zero_copy); let value = value_deserializer.read_value(scope.get_current_context()); match value { @@ -592,6 +695,18 @@ fn queue_microtask( }; } +fn create_host_object( + scope: &mut v8::HandleScope, + _args: v8::FunctionCallbackArguments, + mut rv: v8::ReturnValue, +) { + let template = v8::ObjectTemplate::new(scope); + template.set_internal_field_count(1); + if let Some(obj) = template.new_instance(scope) { + rv.set(obj.into()); + }; +} + /// Called by V8 during `JsRuntime::instantiate_module`. /// /// This function borrows `ModuleMap` from the isolate slot, diff --git a/extensions/web/13_message_port.js b/extensions/web/13_message_port.js index 50d3f5d04b..1928409f20 100644 --- a/extensions/web/13_message_port.js +++ b/extensions/web/13_message_port.js @@ -60,7 +60,9 @@ * @returns {MessagePort} */ function createMessagePort(id) { - const port = webidl.createBranded(MessagePort); + const port = core.createHostObject(); + Object.setPrototypeOf(port, MessagePort.prototype); + port[webidl.brand] = webidl.brand; setEventTargetData(port); port[_id] = id; return port; @@ -187,7 +189,9 @@ } } - const data = core.deserialize(messageData.data); + const data = core.deserialize(messageData.data, { + hostObjects: transferables, + }); return [data, transferables]; } @@ -200,7 +204,7 @@ function serializeJsMessageData(data, tranferables) { let serializedData; try { - serializedData = core.serialize(data); + serializedData = core.serialize(data, { hostObjects: tranferables }); } catch (err) { throw new DOMException(err.message, "DataCloneError"); }