diff --git a/src/binding.cc b/src/binding.cc index 936767e635..f0d34e7830 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -1135,6 +1135,33 @@ void v8__ObjectTemplate__SetAccessorWithSetter( ptr_to_local(&self)->SetAccessor(ptr_to_local(&key), getter, setter); } +void v8__ObjectTemplate__SetNamedPropertyHandler( + const v8::ObjectTemplate& self, + v8::GenericNamedPropertyGetterCallback getter, + v8::GenericNamedPropertySetterCallback setter, + v8::GenericNamedPropertyQueryCallback query, + v8::GenericNamedPropertyDeleterCallback deleter, + v8::GenericNamedPropertyEnumeratorCallback enumerator, + v8::GenericNamedPropertyDescriptorCallback descriptor, + const v8::Value* data_or_null) { + ptr_to_local(&self)->SetHandler(v8::NamedPropertyHandlerConfiguration( + getter, setter, query, deleter, enumerator, nullptr, descriptor, + ptr_to_local(data_or_null))); +} + +void v8__ObjectTemplate__SetIndexedPropertyHandler( + const v8::ObjectTemplate& self, v8::IndexedPropertyGetterCallback getter, + v8::IndexedPropertySetterCallback setter, + v8::IndexedPropertyQueryCallback query, + v8::IndexedPropertyDeleterCallback deleter, + v8::IndexedPropertyEnumeratorCallback enumerator, + v8::IndexedPropertyDescriptorCallback descriptor, + const v8::Value* data_or_null) { + ptr_to_local(&self)->SetHandler(v8::IndexedPropertyHandlerConfiguration( + getter, setter, query, deleter, enumerator, nullptr, descriptor, + ptr_to_local(data_or_null))); +} + void v8__ObjectTemplate__SetAccessorProperty(const v8::ObjectTemplate& self, const v8::Name& key, v8::FunctionTemplate& getter, diff --git a/src/function.rs b/src/function.rs index 47b734fcf8..463a0bcfc9 100644 --- a/src/function.rs +++ b/src/function.rs @@ -400,6 +400,66 @@ where } } +//Should return an Array in Return Value +pub type PropertyEnumeratorCallback<'s> = + extern "C" fn(*const PropertyCallbackInfo); + +impl MapFnFrom for PropertyEnumeratorCallback<'_> +where + F: UnitType + Fn(&mut HandleScope, PropertyCallbackArguments, ReturnValue), +{ + fn mapping() -> Self { + let f = |info: *const PropertyCallbackInfo| { + let info = info as *const PropertyCallbackInfo; + let scope = &mut unsafe { CallbackScope::new(&*info) }; + let args = PropertyCallbackArguments::from_property_callback_info(info); + let rv = ReturnValue::from_property_callback_info(info); + (F::get())(scope, args, rv); + }; + f.to_c_fn() + } +} + +/// IndexedPropertyGetterCallback is used as callback functions when registering a named handler +/// particular property. See Object and ObjectTemplate's method SetHandler. +pub type IndexedPropertyGetterCallback<'s> = + extern "C" fn(u32, *const PropertyCallbackInfo); + +impl MapFnFrom for IndexedPropertyGetterCallback<'_> +where + F: UnitType + + Fn(&mut HandleScope, u32, PropertyCallbackArguments, ReturnValue), +{ + fn mapping() -> Self { + let f = |index: u32, info: *const PropertyCallbackInfo| { + let scope = &mut unsafe { CallbackScope::new(&*info) }; + let args = PropertyCallbackArguments::from_property_callback_info(info); + let rv = ReturnValue::from_property_callback_info(info); + (F::get())(scope, index, args, rv); + }; + f.to_c_fn() + } +} + +pub type IndexedPropertySetterCallback<'s> = + extern "C" fn(u32, Local<'s, Value>, *const PropertyCallbackInfo); + +impl MapFnFrom for IndexedPropertySetterCallback<'_> +where + F: UnitType + + Fn(&mut HandleScope, u32, Local, PropertyCallbackArguments), +{ + fn mapping() -> Self { + let f = + |index: u32, value: Local, info: *const PropertyCallbackInfo| { + let scope = &mut unsafe { CallbackScope::new(&*info) }; + let args = PropertyCallbackArguments::from_property_callback_info(info); + (F::get())(scope, index, value, args); + }; + f.to_c_fn() + } +} + /// A builder to construct the properties of a Function or FunctionTemplate. pub struct FunctionBuilder<'s, T> { pub(crate) callback: FunctionCallback, diff --git a/src/template.rs b/src/template.rs index 240981043c..6498b7c294 100644 --- a/src/template.rs +++ b/src/template.rs @@ -19,9 +19,12 @@ use crate::Function; use crate::FunctionBuilder; use crate::FunctionCallback; use crate::HandleScope; +use crate::IndexedPropertyGetterCallback; +use crate::IndexedPropertySetterCallback; use crate::Local; use crate::Object; use crate::PropertyAttribute; +use crate::PropertyEnumeratorCallback; use crate::SideEffectType; use crate::Signature; use crate::String; @@ -107,9 +110,215 @@ extern "C" { setter: *const FunctionTemplate, attr: PropertyAttribute, ); + + fn v8__ObjectTemplate__SetNamedPropertyHandler( + this: *const ObjectTemplate, + getter: Option, + setter: Option, + query: Option, + deleter: Option, + enumerator: Option, + descriptor: Option, + data_or_null: *const Value, + ); + + fn v8__ObjectTemplate__SetIndexedPropertyHandler( + this: *const ObjectTemplate, + getter: Option, + setter: Option, + query: Option, + deleter: Option, + enumerator: Option, + descriptor: Option, + data_or_null: *const Value, + ); + fn v8__ObjectTemplate__SetImmutableProto(this: *const ObjectTemplate); } +#[derive(Default)] +pub struct NamedPropertyHandlerConfiguration<'s> { + pub(crate) getter: Option>, + pub(crate) setter: Option>, + pub(crate) query: Option>, + pub(crate) deleter: Option>, + pub(crate) enumerator: Option>, + pub(crate) descriptor: Option>, + pub(crate) data: Option>, +} + +impl<'s> NamedPropertyHandlerConfiguration<'s> { + pub fn new() -> Self { + Self { + getter: None, + setter: None, + query: None, + deleter: None, + enumerator: None, + descriptor: None, + data: None, + } + } + + pub fn is_some(&self) -> bool { + self.getter.is_some() + || self.setter.is_some() + || self.query.is_some() + || self.deleter.is_some() + || self.enumerator.is_some() + || self.descriptor.is_some() + } + + pub fn getter( + mut self, + getter: impl MapFnTo>, + ) -> Self { + self.getter = Some(getter.map_fn_to()); + self + } + + pub fn setter( + mut self, + setter: impl MapFnTo>, + ) -> Self { + self.setter = Some(setter.map_fn_to()); + self + } + + // Intercepts all requests that query the attributes of the property, + // e.g., getOwnPropertyDescriptor(), propertyIsEnumerable(), and defineProperty() + // Use ReturnValue.set_int32(value) to set the property attributes. The value is an interger encoding a v8::PropertyAttribute. + pub fn query( + mut self, + query: impl MapFnTo>, + ) -> Self { + self.query = Some(query.map_fn_to()); + self + } + // Interceptor for delete requests on an object. + // Use ReturnValue.set_bool to indicate whether the request was intercepted or not. If the deleter successfully intercepts the request, + // i.e., if the request should not be further executed, call info.GetReturnValue().Set(value) with a boolean value. + // The value is used as the return value of delete. + pub fn deleter( + mut self, + deleter: impl MapFnTo>, + ) -> Self { + self.deleter = Some(deleter.map_fn_to()); + self + } + // Returns an array containing the names of the properties the named property getter intercepts. + // use ReturnValue.set with a v8::Array + pub fn enumerator( + mut self, + enumerator: impl MapFnTo>, + ) -> Self { + self.enumerator = Some(enumerator.map_fn_to()); + self + } + + pub fn descriptor( + mut self, + descriptor: impl MapFnTo>, + ) -> Self { + self.descriptor = Some(descriptor.map_fn_to()); + self + } + + /// Set the associated data. The default is no associated data. + pub fn data(mut self, data: Local<'s, Value>) -> Self { + self.data = Some(data); + self + } +} + +#[derive(Default)] +pub struct IndexedPropertyHandlerConfiguration<'s> { + pub(crate) getter: Option>, + pub(crate) setter: Option>, + pub(crate) query: Option>, + pub(crate) deleter: Option>, + pub(crate) enumerator: Option>, + pub(crate) descriptor: Option>, + pub(crate) data: Option>, +} + +impl<'s> IndexedPropertyHandlerConfiguration<'s> { + pub fn new() -> Self { + Self { + getter: None, + setter: None, + query: None, + deleter: None, + enumerator: None, + descriptor: None, + data: None, + } + } + + pub fn is_some(&self) -> bool { + self.getter.is_some() + || self.setter.is_some() + || self.query.is_some() + || self.deleter.is_some() + || self.enumerator.is_some() + || self.descriptor.is_some() + } + + pub fn getter( + mut self, + getter: impl MapFnTo>, + ) -> Self { + self.getter = Some(getter.map_fn_to()); + self + } + + pub fn setter( + mut self, + setter: impl MapFnTo>, + ) -> Self { + self.setter = Some(setter.map_fn_to()); + self + } + + pub fn query( + mut self, + query: impl MapFnTo>, + ) -> Self { + self.query = Some(query.map_fn_to()); + self + } + + pub fn deleter( + mut self, + deleter: impl MapFnTo>, + ) -> Self { + self.deleter = Some(deleter.map_fn_to()); + self + } + + pub fn enumerator( + mut self, + enumerator: impl MapFnTo>, + ) -> Self { + self.enumerator = Some(enumerator.map_fn_to()); + self + } + + pub fn descriptor( + mut self, + descriptor: impl MapFnTo>, + ) -> Self { + self.descriptor = Some(descriptor.map_fn_to()); + self + } + + /// Set the associated data. The default is no associated data. + pub fn data(mut self, data: Local<'s, Value>) -> Self { + self.data = Some(data); + self + } +} + impl Template { /// Adds a property to each instance created by this template. #[inline(always)] @@ -413,6 +622,46 @@ impl ObjectTemplate { } } + //Re uses the AccessorNameGetterCallback to avoid implementation conflicts since the declaration for + //GenericNamedPropertyGetterCallback and AccessorNameGetterCallback are the same + pub fn set_named_property_handler( + &self, + configuration: NamedPropertyHandlerConfiguration, + ) { + assert!(configuration.is_some()); + unsafe { + v8__ObjectTemplate__SetNamedPropertyHandler( + self, + configuration.getter, + configuration.setter, + configuration.query, + configuration.deleter, + configuration.enumerator, + configuration.descriptor, + configuration.data.map_or_else(null, |p| &*p), + ) + } + } + + pub fn set_indexed_property_handler( + &self, + configuration: IndexedPropertyHandlerConfiguration, + ) { + assert!(configuration.is_some()); + unsafe { + v8__ObjectTemplate__SetIndexedPropertyHandler( + self, + configuration.getter, + configuration.setter, + configuration.query, + configuration.deleter, + configuration.enumerator, + configuration.descriptor, + configuration.data.map_or_else(null, |p| &*p), + ) + } + } + /// Sets an [accessor property](https://tc39.es/ecma262/#sec-property-attributes) /// on the object template. /// diff --git a/tests/test_api.rs b/tests/test_api.rs index 7a9a8ce7a9..a525f31fd3 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -1762,6 +1762,305 @@ fn object_template_set_accessor() { } } +#[test] +fn object_template_set_named_property_handler() { + 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); + + { + 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, "key").unwrap(); + assert!(key.strict_equals(expected_key.into())); + + rv.set(this.get_internal_field(scope, 0).unwrap()); + }; + + let setter = |scope: &mut v8::HandleScope, + key: v8::Local, + value: v8::Local, + args: v8::PropertyCallbackArguments| { + let this = args.this(); + + let expected_key = v8::String::new(scope, "key").unwrap(); + assert!(key.strict_equals(expected_key.into())); + + assert!(value.is_int32()); + assert!(this.set_internal_field(0, value)); + }; + + let query = |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, "key").unwrap(); + assert!(key.strict_equals(expected_key.into())); + //PropertyAttribute::READ_ONLY + rv.set_int32(1); + let expected_value = v8::Integer::new(scope, 42); + assert!(this + .get_internal_field(scope, 0) + .unwrap() + .strict_equals(expected_value.into())); + }; + let deleter = |scope: &mut v8::HandleScope, + key: v8::Local, + _args: v8::PropertyCallbackArguments, + mut rv: v8::ReturnValue| { + let expected_key = v8::String::new(scope, "key").unwrap(); + assert!(key.strict_equals(expected_key.into())); + + rv.set_bool(true); + }; + + let enumerator = |scope: &mut v8::HandleScope, + args: v8::PropertyCallbackArguments, + mut rv: v8::ReturnValue| { + let this = args.this(); + //Validate is the current object + let expected_value = v8::Integer::new(scope, 42); + assert!(this + .get_internal_field(scope, 0) + .unwrap() + .strict_equals(expected_value.into())); + + let key = v8::String::new(scope, "key").unwrap(); + let result = v8::Array::new_with_elements(scope, &[key.into()]); + rv.set(result.into()); + }; + + let name = v8::String::new(scope, "obj").unwrap(); + + // Lone getter + let templ = v8::ObjectTemplate::new(scope); + templ.set_internal_field_count(1); + templ.set_named_property_handler( + v8::NamedPropertyHandlerConfiguration::new().getter(getter), + ); + + let obj = templ.new_instance(scope).unwrap(); + let int = v8::Integer::new(scope, 42); + obj.set_internal_field(0, int.into()); + scope.get_current_context().global(scope).set( + scope, + name.into(), + obj.into(), + ); + assert!(eval(scope, "obj.key").unwrap().strict_equals(int.into())); + + // Getter + setter + deleter + let templ = v8::ObjectTemplate::new(scope); + templ.set_internal_field_count(1); + templ.set_named_property_handler( + v8::NamedPropertyHandlerConfiguration::new() + .getter(getter) + .setter(setter) + .deleter(deleter), + ); + + let obj = templ.new_instance(scope).unwrap(); + obj.set_internal_field(0, int.into()); + scope.get_current_context().global(scope).set( + scope, + name.into(), + obj.into(), + ); + let new_int = v8::Integer::new(scope, 9); + eval(scope, "obj.key = 9"); + assert!(obj + .get_internal_field(scope, 0) + .unwrap() + .strict_equals(new_int.into())); + + assert!(eval(scope, "delete obj.key").unwrap().boolean_value(scope)); + + // query descriptor + let templ = v8::ObjectTemplate::new(scope); + templ.set_internal_field_count(1); + templ.set_named_property_handler( + v8::NamedPropertyHandlerConfiguration::new().query(query), + ); + + let obj = templ.new_instance(scope).unwrap(); + obj.set_internal_field(0, int.into()); + scope.get_current_context().global(scope).set( + scope, + name.into(), + obj.into(), + ); + let result = + eval(scope, "Object.getOwnPropertyDescriptor(obj, 'key')").unwrap(); + let object = result.to_object(scope).unwrap(); + let key = v8::String::new(scope, "writable").unwrap(); + let value = object.get(scope, key.into()).unwrap(); + + let non_writable = v8::Boolean::new(scope, false); + assert!(value.strict_equals(non_writable.into())); + + //enumerator + let templ = v8::ObjectTemplate::new(scope); + templ.set_internal_field_count(1); + templ.set_named_property_handler( + v8::NamedPropertyHandlerConfiguration::new().enumerator(enumerator), + ); + + let obj = templ.new_instance(scope).unwrap(); + obj.set_internal_field(0, int.into()); + scope.get_current_context().global(scope).set( + scope, + name.into(), + obj.into(), + ); + let arr = v8::Local::::try_from( + eval(scope, "Object.keys(obj)").unwrap(), + ) + .unwrap(); + assert_eq!(arr.length(), 1); + let index = v8::Integer::new(scope, 0); + let result = arr.get(scope, index.into()).unwrap(); + let expected = v8::String::new(scope, "key").unwrap(); + assert!(expected.strict_equals(result)) + } +} + +#[test] +fn object_template_set_indexed_property_handler() { + 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); + + let getter = |scope: &mut v8::HandleScope, + index: u32, + args: v8::PropertyCallbackArguments, + mut rv: v8::ReturnValue| { + let this = args.this(); + let expected_index = 37; + assert!(index.eq(&expected_index)); + rv.set(this.get_internal_field(scope, 0).unwrap()); + }; + + let setter = |_scope: &mut v8::HandleScope, + index: u32, + value: v8::Local, + args: v8::PropertyCallbackArguments| { + let this = args.this(); + + assert_eq!(index, 37); + + assert!(value.is_int32()); + assert!(this.set_internal_field(0, value)); + }; + + let deleter = |_scope: &mut v8::HandleScope, + index: u32, + _args: v8::PropertyCallbackArguments, + mut rv: v8::ReturnValue| { + assert_eq!(index, 37); + + rv.set_bool(false); + }; + + let enumerator = |scope: &mut v8::HandleScope, + args: v8::PropertyCallbackArguments, + mut rv: v8::ReturnValue| { + let this = args.this(); + //Validate is the current object + let expected_value = v8::Integer::new(scope, 42); + assert!(this + .get_internal_field(scope, 0) + .unwrap() + .strict_equals(expected_value.into())); + + let key = v8::Integer::new(scope, 37); + let result = v8::Array::new_with_elements(scope, &[key.into()]); + rv.set(result.into()); + }; + + let name = v8::String::new(scope, "obj").unwrap(); + + // Lone getter + let templ = v8::ObjectTemplate::new(scope); + templ.set_internal_field_count(1); + templ.set_indexed_property_handler( + v8::IndexedPropertyHandlerConfiguration::new().getter(getter), + ); + + let obj = templ.new_instance(scope).unwrap(); + let int = v8::Integer::new(scope, 42); + obj.set_internal_field(0, int.into()); + scope + .get_current_context() + .global(scope) + .set(scope, name.into(), obj.into()); + assert!(eval(scope, "obj[37]").unwrap().strict_equals(int.into())); + + // Getter + setter + deleter + let templ = v8::ObjectTemplate::new(scope); + templ.set_internal_field_count(1); + templ.set_indexed_property_handler( + v8::IndexedPropertyHandlerConfiguration::new() + .getter(getter) + .setter(setter) + .deleter(deleter), + ); + + let obj = templ.new_instance(scope).unwrap(); + obj.set_internal_field(0, int.into()); + scope + .get_current_context() + .global(scope) + .set(scope, name.into(), obj.into()); + let new_int = v8::Integer::new(scope, 9); + eval(scope, "obj[37] = 9"); + assert!(obj + .get_internal_field(scope, 0) + .unwrap() + .strict_equals(new_int.into())); + + assert!(!eval(scope, "delete obj[37]").unwrap().boolean_value(scope)); + + //Enumerator + let templ = v8::ObjectTemplate::new(scope); + templ.set_internal_field_count(1); + templ.set_indexed_property_handler( + v8::IndexedPropertyHandlerConfiguration::new() + .getter(getter) + .enumerator(enumerator), + ); + + let obj = templ.new_instance(scope).unwrap(); + obj.set_internal_field(0, int.into()); + scope + .get_current_context() + .global(scope) + .set(scope, name.into(), obj.into()); + + let value = eval( + scope, + " + let value = -1; + for (const i in obj) { + value = obj[i]; + } + value + ", + ) + .unwrap(); + + assert!(value.strict_equals(int.into())); +} + #[test] fn object() { let _setup_guard = setup();