From e74b6bcd0d14c495514342325fdf4fdad0ebea34 Mon Sep 17 00:00:00 2001 From: Ben Sheffield Date: Fri, 17 Jul 2020 16:17:29 +0100 Subject: [PATCH] Add 'Object::set_accessor_with_setter' (#422) --- src/binding.cc | 9 +++++ src/function.rs | 20 +++++++++++ src/object.rs | 27 ++++++++++++++ tests/test_api.rs | 92 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 148 insertions(+) diff --git a/src/binding.cc b/src/binding.cc index 02bbe861..c05c530a 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -743,6 +743,15 @@ MaybeBool v8__Object__SetAccessor(const v8::Object& self, ptr_to_local(&context), ptr_to_local(&key), getter)); } +MaybeBool v8__Object__SetAccessorWithSetter(const v8::Object& self, + const v8::Context& context, + const v8::Name& key, + v8::AccessorNameGetterCallback getter, + v8::AccessorNameSetterCallback setter) { + return maybe_to_maybe_bool(ptr_to_local(&self)->SetAccessor( + ptr_to_local(&context), ptr_to_local(&key), getter, setter)); +} + v8::Isolate* v8__Object__GetIsolate(const v8::Object& self) { return ptr_to_local(&self)->GetIsolate(); } diff --git a/src/function.rs b/src/function.rs index 1f3f260b..61d4066b 100644 --- a/src/function.rs +++ b/src/function.rs @@ -258,6 +258,26 @@ where } } +pub type AccessorNameSetterCallback<'s> = + extern "C" fn(Local<'s, Name>, Local<'s, Value>, *const PropertyCallbackInfo); + +impl MapFnFrom for AccessorNameSetterCallback<'_> +where + F: UnitType + + Fn(&mut HandleScope, Local, Local, PropertyCallbackArguments), +{ + fn mapping() -> Self { + let f = |key: Local, + value: Local, + info: *const PropertyCallbackInfo| { + let scope = &mut unsafe { CallbackScope::new(&*info) }; + let args = PropertyCallbackArguments::from_property_callback_info(info); + (F::get())(scope, key, value, args); + }; + f.to_c_fn() + } +} + impl Function { // TODO: add remaining arguments from C++ /// Create a function in the current execution context diff --git a/src/object.rs b/src/object.rs index b0349b4d..ee8c339f 100644 --- a/src/object.rs +++ b/src/object.rs @@ -3,6 +3,7 @@ use crate::support::int; use crate::support::MapFnTo; use crate::support::MaybeBool; use crate::AccessorNameGetterCallback; +use crate::AccessorNameSetterCallback; use crate::Array; use crate::Context; use crate::HandleScope; @@ -28,6 +29,13 @@ extern "C" { key: *const Name, getter: AccessorNameGetterCallback, ) -> MaybeBool; + fn v8__Object__SetAccessorWithSetter( + this: *const Object, + context: *const Context, + key: *const Name, + getter: AccessorNameGetterCallback, + setter: AccessorNameSetterCallback, + ) -> MaybeBool; fn v8__Object__Get( this: *const Object, context: *const Context, @@ -286,6 +294,25 @@ impl Object { .into() } + pub fn set_accessor_with_setter( + &self, + scope: &mut HandleScope, + name: Local, + getter: impl for<'s> MapFnTo>, + setter: impl for<'s> MapFnTo>, + ) -> Option { + unsafe { + v8__Object__SetAccessorWithSetter( + self, + &*scope.get_current_context(), + &*name, + getter.map_fn_to(), + setter.map_fn_to(), + ) + } + .into() + } + /// The `Object` specific equivalent of `Data::get_hash()`. /// This function is kept around for testing purposes only. #[doc(hidden)] diff --git a/tests/test_api.rs b/tests/test_api.rs index 0e62be39..d4117f6a 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -1345,6 +1345,98 @@ fn object_set_accessor() { } } +#[test] +fn object_set_accessor_with_setter() { + let _setup_guard = setup(); + 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); + + { + static CALL_COUNT: AtomicUsize = AtomicUsize::new(0); + + let getter = |scope: &mut v8::HandleScope, + key: v8::Local, + args: v8::PropertyCallbackArguments, + mut rv: v8::ReturnValue| { + let this = args.this(); + + let expected_key = v8::String::new(scope, "getter_setter_key").unwrap(); + assert!(key.strict_equals(expected_key.into())); + + let int_key = v8::String::new(scope, "int_key").unwrap(); + let int_value = this.get(scope, int_key.into()).unwrap(); + let int_value = v8::Local::::try_from(int_value).unwrap(); + assert_eq!(int_value.value(), 42); + + let s = v8::String::new(scope, "hello").unwrap(); + assert!(rv.get(scope).is_undefined()); + rv.set(s.into()); + + CALL_COUNT.fetch_add(1, Ordering::SeqCst); + }; + + let setter = |scope: &mut v8::HandleScope, + key: v8::Local, + value: v8::Local, + args: v8::PropertyCallbackArguments| { + println!("setter called"); + let this = args.this(); + + let expected_key = v8::String::new(scope, "getter_setter_key").unwrap(); + assert!(key.strict_equals(expected_key.into())); + + let int_key = v8::String::new(scope, "int_key").unwrap(); + let int_value = this.get(scope, int_key.into()).unwrap(); + let int_value = v8::Local::::try_from(int_value).unwrap(); + assert_eq!(int_value.value(), 42); + + let new_value = v8::Local::::try_from(value).unwrap(); + this.set(scope, int_key.into(), new_value.into()); + + CALL_COUNT.fetch_add(1, Ordering::SeqCst); + }; + + let obj = v8::Object::new(scope); + + let getter_setter_key = + v8::String::new(scope, "getter_setter_key").unwrap(); + obj.set_accessor_with_setter( + scope, + getter_setter_key.into(), + getter, + setter, + ); + + let int_key = v8::String::new(scope, "int_key").unwrap(); + let int_value = v8::Integer::new(scope, 42); + obj.set(scope, int_key.into(), int_value.into()); + + let obj_name = v8::String::new(scope, "obj").unwrap(); + context + .global(scope) + .set(scope, obj_name.into(), obj.into()); + + let actual = eval(scope, "obj.getter_setter_key").unwrap(); + let expected = v8::String::new(scope, "hello").unwrap(); + assert!(actual.strict_equals(expected.into())); + + eval(scope, "obj.getter_setter_key = 123").unwrap(); + assert_eq!( + obj + .get(scope, int_key.into()) + .unwrap() + .to_integer(scope) + .unwrap() + .value(), + 123 + ); + + assert_eq!(CALL_COUNT.load(Ordering::SeqCst), 2); + } +} + #[test] fn promise_resolved() { let _setup_guard = setup();