Skip to content

Commit

Permalink
feat: Support custom host objects in ValueSerializer (#1322)
Browse files Browse the repository at this point in the history
Add v8::ValueSerializerImpl::{has_custom_host_object,is_host_object} 
equivalents for v8::ValueSerializer::Delegate::{HasCustomHostObject,IsCustomHostObject}.

This enables serializing custom host objects without embedder fields.
  • Loading branch information
lrowe authored Nov 20, 2023
1 parent 75a2646 commit ec2c901
Show file tree
Hide file tree
Showing 3 changed files with 266 additions and 7 deletions.
17 changes: 17 additions & 0 deletions src/binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3197,6 +3197,13 @@ extern "C" {
void v8__ValueSerializer__Delegate__ThrowDataCloneError(
v8::ValueSerializer::Delegate* self, v8::Local<v8::String> message);

bool v8__ValueSerializer__Delegate__HasCustomHostObject(
v8::ValueSerializer::Delegate* self, v8::Isolate* isolate);

MaybeBool v8__ValueSerializer__Delegate__IsHostObject(
v8::ValueSerializer::Delegate* self, v8::Isolate* isolate,
v8::Local<v8::Object> object);

MaybeBool v8__ValueSerializer__Delegate__WriteHostObject(
v8::ValueSerializer::Delegate* self, v8::Isolate* isolate,
v8::Local<v8::Object> object);
Expand All @@ -3222,6 +3229,16 @@ struct v8__ValueSerializer__Delegate : public v8::ValueSerializer::Delegate {
v8__ValueSerializer__Delegate__ThrowDataCloneError(this, message);
}

bool HasCustomHostObject(v8::Isolate* isolate) override {
return v8__ValueSerializer__Delegate__HasCustomHostObject(this, isolate);
}

v8::Maybe<bool> IsHostObject(v8::Isolate* isolate,
v8::Local<v8::Object> object) override {
return maybe_bool_to_maybe(
v8__ValueSerializer__Delegate__IsHostObject(this, isolate, object));
}

v8::Maybe<bool> WriteHostObject(v8::Isolate* isolate,
v8::Local<v8::Object> object) override {
return maybe_bool_to_maybe(
Expand Down
43 changes: 43 additions & 0 deletions src/value_serializer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,32 @@ pub unsafe extern "C" fn v8__ValueSerializer__Delegate__ThrowDataCloneError(
.throw_data_clone_error(scope, message)
}

#[no_mangle]
pub unsafe extern "C" fn v8__ValueSerializer__Delegate__HasCustomHostObject(
this: &mut CxxValueSerializerDelegate,
isolate: *mut Isolate,
) -> bool {
let value_serializer_heap = ValueSerializerHeap::dispatch_mut(this);
value_serializer_heap
.value_serializer_impl
.as_mut()
.has_custom_host_object(&mut *isolate)
}

#[no_mangle]
pub unsafe extern "C" fn v8__ValueSerializer__Delegate__IsHostObject(
this: &mut CxxValueSerializerDelegate,
_isolate: *mut Isolate,
object: Local<Object>,
) -> MaybeBool {
let value_serializer_heap = ValueSerializerHeap::dispatch_mut(this);
let scope =
&mut crate::scope::CallbackScope::new(value_serializer_heap.context);
let value_serializer_impl =
value_serializer_heap.value_serializer_impl.as_mut();
MaybeBool::from(value_serializer_impl.is_host_object(scope, object))
}

#[no_mangle]
pub unsafe extern "C" fn v8__ValueSerializer__Delegate__WriteHostObject(
this: &mut CxxValueSerializerDelegate,
Expand Down Expand Up @@ -211,6 +237,23 @@ pub trait ValueSerializerImpl {
message: Local<'s, String>,
);

fn has_custom_host_object(&mut self, _isolate: &mut Isolate) -> bool {
false
}

fn is_host_object<'s>(
&mut self,
scope: &mut HandleScope<'s>,
_object: Local<'s, Object>,
) -> Option<bool> {
let msg =
String::new(scope, "Deno serializer: is_host_object not implemented")
.unwrap();
let exc = Exception::error(scope, msg);
scope.throw_exception(exc);
None
}

fn write_host_object<'s>(
&mut self,
scope: &mut HandleScope<'s>,
Expand Down
213 changes: 206 additions & 7 deletions tests/test_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7731,12 +7731,18 @@ impl<'a> v8::ValueSerializerImpl for Custom1Value<'a> {

fn write_host_object<'s>(
&mut self,
_scope: &mut v8::HandleScope<'s>,
_object: v8::Local<'s, v8::Object>,
scope: &mut v8::HandleScope<'s>,
object: v8::Local<'s, v8::Object>,
value_serializer: &mut dyn v8::ValueSerializerHelper,
) -> Option<bool> {
value_serializer.write_uint64(1);
None
let key = v8::String::new(scope, "hostObject").unwrap();
let value = object
.get(scope, key.into())
.unwrap()
.uint32_value(scope)
.unwrap();
value_serializer.write_uint32(value);
Some(true)
}
}

Expand All @@ -7756,12 +7762,18 @@ impl<'a> v8::ValueDeserializerImpl for Custom1Value<'a> {

fn read_host_object<'s>(
&mut self,
_scope: &mut v8::HandleScope<'s>,
scope: &mut v8::HandleScope<'s>,
value_deserializer: &mut dyn v8::ValueDeserializerHelper,
) -> Option<v8::Local<'s, v8::Object>> {
let mut value = 0;
value_deserializer.read_uint64(&mut value);
None
value_deserializer.read_uint32(&mut value);
let template = v8::ObjectTemplate::new(scope);
template.set_internal_field_count(1);
let host_object = template.new_instance(scope).unwrap();
let key = v8::String::new(scope, "readHostObject").unwrap();
let value = v8::Integer::new_from_unsigned(scope, value);
host_object.set(scope, key.into(), value.into());
Some(host_object)
}
}

Expand Down Expand Up @@ -7985,6 +7997,64 @@ fn value_serializer_and_deserializer_array_buffers() {
}
}

#[test]
fn value_serializer_and_deserializer_embedder_host_object() {
let buffer;
let expected: u32 = 123;
let mut array_buffers = ArrayBuffers::new();
{
let _setup_guard = setup::parallel_test();
let isolate = &mut v8::Isolate::new(Default::default());

let scope = &mut v8::HandleScope::new(isolate);

let context = v8::Context::new(scope);
let scope = &mut v8::ContextScope::new(scope, context);

let template = v8::ObjectTemplate::new(scope);
template.set_internal_field_count(1);
let host_object = template.new_instance(scope).unwrap();
let key = v8::String::new(scope, "hostObject").unwrap();
let value = v8::Integer::new_from_unsigned(scope, expected);
host_object.set(scope, key.into(), value.into());

let mut value_serializer =
Custom1Value::serializer(scope, &mut array_buffers);
assert_eq!(
value_serializer.write_value(context, host_object.into()),
Some(true)
);

buffer = value_serializer.release();
}

{
let _setup_guard = setup::parallel_test();
let isolate = &mut v8::Isolate::new(Default::default());

let scope = &mut v8::HandleScope::new(isolate);

let context = v8::Context::new(scope);
let scope = &mut v8::ContextScope::new(scope, context);

let mut value_deserializer =
Custom1Value::deserializer(scope, &buffer, &mut array_buffers);
let host_object_out = value_deserializer
.read_value(context)
.unwrap()
.to_object(scope)
.unwrap();
drop(value_deserializer);
let key = v8::String::new(scope, "readHostObject").unwrap();
let value = host_object_out
.get(scope, key.into())
.unwrap()
.uint32_value(scope)
.unwrap();
assert_eq!(value, expected);
}
}

struct Custom2Value {}

impl<'a> Custom2Value {
Expand Down Expand Up @@ -8044,6 +8114,135 @@ fn value_serializer_not_implemented() {
);
}

struct Custom3Value {}

impl<'a> Custom3Value {
fn serializer<'s>(
scope: &mut v8::HandleScope<'s>,
) -> v8::ValueSerializer<'a, 's> {
v8::ValueSerializer::new(scope, Box::new(Self {}))
}

fn deserializer<'s>(
scope: &mut v8::HandleScope<'s>,
data: &[u8],
) -> v8::ValueDeserializer<'a, 's> {
v8::ValueDeserializer::new(scope, Box::new(Self {}), data)
}
}

impl v8::ValueSerializerImpl for Custom3Value {
#[allow(unused_variables)]
fn throw_data_clone_error<'s>(
&mut self,
scope: &mut v8::HandleScope<'s>,
message: v8::Local<'s, v8::String>,
) {
let error = v8::Exception::error(scope, message);
scope.throw_exception(error);
}

fn has_custom_host_object(&mut self, _isolate: &mut v8::Isolate) -> bool {
true
}

fn is_host_object<'s>(
&mut self,
scope: &mut v8::HandleScope<'s>,
object: v8::Local<'s, v8::Object>,
) -> Option<bool> {
let key = v8::String::new(scope, "hostObject").unwrap();
object.has_own_property(scope, key.into())
}

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<bool> {
let key = v8::String::new(scope, "hostObject").unwrap();
let value = object
.get(scope, key.into())
.unwrap()
.uint32_value(scope)
.unwrap();
value_serializer.write_uint32(value);
Some(true)
}
}

impl v8::ValueDeserializerImpl for Custom3Value {
fn read_host_object<'s>(
&mut self,
scope: &mut v8::HandleScope<'s>,
value_deserializer: &mut dyn v8::ValueDeserializerHelper,
) -> Option<v8::Local<'s, v8::Object>> {
let mut value = 0;
value_deserializer.read_uint32(&mut value);
let host_object = v8::Object::new(scope);
let key = v8::String::new(scope, "readHostObject").unwrap();
let value = v8::Integer::new_from_unsigned(scope, value);
host_object.set(scope, key.into(), value.into());
Some(host_object)
}
}

#[test]
fn value_serializer_and_deserializer_custom_host_object() {
let buffer;
let expected: u32 = 123;
{
let _setup_guard = setup::parallel_test();
let isolate = &mut v8::Isolate::new(Default::default());

let scope = &mut v8::HandleScope::new(isolate);

let context = v8::Context::new(scope);
let scope = &mut v8::ContextScope::new(scope, context);

let host_object = v8::Object::new(scope);
let key = v8::String::new(scope, "hostObject").unwrap();
let value = v8::Integer::new_from_unsigned(scope, expected);
host_object.set(scope, key.into(), value.into());

let mut value_serializer = Custom3Value::serializer(scope);
assert_eq!(
value_serializer.write_value(context, host_object.into()),
Some(true)
);

buffer = value_serializer.release();
}

{
let _setup_guard = setup::parallel_test();
let isolate = &mut v8::Isolate::new(Default::default());

let scope = &mut v8::HandleScope::new(isolate);

let context = v8::Context::new(scope);
let scope = &mut v8::ContextScope::new(scope, context);

let mut value_deserializer = Custom3Value::deserializer(scope, &buffer);
let host_object_out = value_deserializer
.read_value(context)
.unwrap()
.to_object(scope)
.unwrap();
drop(value_deserializer);
let key = v8::String::new(scope, "readHostObject").unwrap();
let has_prop = host_object_out.has_own_property(scope, key.into()).unwrap();
assert!(has_prop);
let value = host_object_out
.get(scope, key.into())
.unwrap()
.uint32_value(scope)
.unwrap();
assert_eq!(value, expected);
}
}

#[test]
fn memory_pressure_notification() {
let _setup_guard = setup::parallel_test();
Expand Down

0 comments on commit ec2c901

Please sign in to comment.