diff --git a/src/object.rs b/src/object.rs index 1c238c5..62663ac 100644 --- a/src/object.rs +++ b/src/object.rs @@ -5,7 +5,7 @@ // except according to those terms. use super::{JSObject, JSString}; -use crate::{sys, JSContext, JSException, JSValue}; +use crate::{sys, JSException, JSValue}; use std::ops::Deref; use std::ptr; @@ -134,6 +134,92 @@ impl JSObject { } } + /// Set a property onto an object. + /// + /// This can be used to create a new property, or to update an existing property. + /// + /// * `index`: A value that can be converted to a [`JSString`] containing + /// the property's name. + /// * `value`: A value containig the property's value. + /// + /// Calling `get_property_at_index` is equivalent to calling + /// `get_property` with a string containing `index`, + /// but `get_property_at_index` provides optimized access to + /// numeric properties. + /// + /// ``` + /// # use javascriptcore::{JSContext, JSValue}; + /// let ctx = JSContext::default(); + /// let object = JSValue::new_from_json(&ctx, r#"{"a": 10}"#).expect("valid object").as_object().unwrap(); + /// object.set_property("b", JSValue::new_number(&ctx, 11.)).unwrap(); + /// + /// assert!(object.has_property("a")); + /// assert!(object.has_property("b")); + /// ``` + pub fn set_property(&self, name: S, value: JSValue) -> Result<(), JSException> + where + S: Into, + { + let name: JSString = name.into(); + let mut exception: sys::JSValueRef = ptr::null_mut(); + let context = self.value.ctx; + + unsafe { + sys::JSObjectSetProperty(context, self.raw, name.raw, value.raw, 0, &mut exception) + } + + if !exception.is_null() { + return Err(JSException { + value: JSValue { + raw: exception, + ctx: context, + }, + }); + } + + Ok(()) + } + + /// Set a property onto an object by using a numeric index. + /// + /// This can be used to create a new property, or to update an existing property. + /// + /// * `index`: An integer value that is the property's name. + /// * `value`: A value containig the property's value. + /// + /// Calling `set_property_at_index` is equivalent to calling `set_property` with + /// a string containing `index`, but `set_property_at_index` provides optimized + /// access to numeric properties. + /// + /// ``` + /// # use javascriptcore::{JSContext, JSValue}; + /// let ctx = JSContext::default(); + /// let object = JSValue::new_from_json(&ctx, r#"[10]"#).expect("valid array").as_object().unwrap(); + /// object.set_property_at_index(1, JSValue::new_number(&ctx, 11.)).unwrap(); + /// + /// assert!(object.has_property("0")); + /// assert!(object.has_property("1")); + /// ``` + pub fn set_property_at_index(&self, index: u32, value: JSValue) -> Result<(), JSException> { + let mut exception: sys::JSValueRef = ptr::null_mut(); + let context = self.value.ctx; + + unsafe { + sys::JSObjectSetPropertyAtIndex(context, self.raw, index, value.raw, &mut exception) + } + + if !exception.is_null() { + return Err(JSException { + value: JSValue { + raw: exception, + ctx: context, + }, + }); + } + + Ok(()) + } + /// Call this object considering it is a valid function. /// /// ```rust @@ -144,7 +230,6 @@ impl JSObject { /// let pow = math.get_property("pow").as_object().unwrap(); /// /// let result = pow.call_as_function( - /// &ctx, /// None, /// &[JSValue::new_number(&ctx, 2.), JSValue::new_number(&ctx, 3.)], /// ).unwrap(); @@ -153,7 +238,6 @@ impl JSObject { /// ``` pub fn call_as_function( &self, - context: &JSContext, this: Option<&JSObject>, arguments: &[JSValue], ) -> Result { @@ -162,10 +246,11 @@ impl JSObject { .map(|argument| argument.raw) .collect::>(); let mut exception: sys::JSValueRef = ptr::null_mut(); + let context = self.value.ctx; let result = unsafe { sys::JSObjectCallAsFunction( - context.raw, + context, self.raw, this.map(|this| this.raw).unwrap_or_else(ptr::null_mut), arguments.len(), @@ -178,23 +263,31 @@ impl JSObject { return Err(JSException { value: JSValue { raw: exception, - ctx: context.raw, + ctx: context, }, }); } if result.is_null() { return Err(JSException { - value: JSValue::new_string( - context, - "Cannot call this object as a function: it is not a valid function", - ), + value: JSValue { + raw: unsafe { + sys::JSValueMakeString( + context, + JSString::from( + "Cannot call this object as a function: it is not a valid function", + ) + .raw, + ) + }, + ctx: context, + }, }); } Ok(JSValue { raw: result, - ctx: context.raw, + ctx: context, }) } } @@ -280,6 +373,42 @@ mod tests { assert_eq!(names[0], "id"); } + #[test] + fn can_set_property() -> Result<(), JSException> { + let ctx = JSContext::default(); + let object = JSValue::new_from_json(&ctx, r#"{"foo": "bar"}"#) + .unwrap() + .as_object()?; + + assert!(object.has_property("foo")); + assert!(!object.has_property("baz")); + + object.set_property("baz", JSValue::new_string(&ctx, "qux"))?; + + assert!(object.has_property("baz")); + assert_eq!(object.get_property("baz").as_string()?.to_string(), "qux"); + + Ok(()) + } + + #[test] + fn can_set_property_at_index() -> Result<(), JSException> { + let ctx = JSContext::default(); + let object = JSValue::new_from_json(&ctx, r#"[10]"#) + .unwrap() + .as_object()?; + + assert!(object.has_property("0")); + assert!(!object.has_property("1")); + + object.set_property_at_index(1, JSValue::new_number(&ctx, 11.))?; + + assert!(object.has_property("1")); + assert_eq!(object.get_property_at_index(1).as_number()?, 11.); + + Ok(()) + } + #[test] fn can_use_as_jsvalue_via_deref() { let ctx = JSContext::default(); @@ -290,14 +419,13 @@ mod tests { } #[test] - fn call_as_function() -> Result<(), JSException> { + fn can_call_as_function() -> Result<(), JSException> { let ctx = JSContext::default(); let global = ctx.global_object()?; let math = global.get_property("Math").as_object()?; let pow = math.get_property("pow").as_object()?; let result = pow.call_as_function( - &ctx, None, &[JSValue::new_number(&ctx, 2.), JSValue::new_number(&ctx, 3.)], )?; @@ -307,7 +435,7 @@ mod tests { // Not a function, it's a constant. let e = math.get_property("E").as_object()?; - assert!(e.call_as_function(&ctx, None, &[]).is_err()); + assert!(e.call_as_function(None, &[]).is_err()); Ok(()) }