/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ //! Conversions of Rust values to and from `JSVal`. //! //! | IDL type | Argument type | Return type | //! |-------------------------|-----------------|----------------| //! | any | `JSVal` | //! | boolean | `bool` | //! | byte | `i8` | //! | octet | `u8` | //! | short | `i16` | //! | unsigned short | `u16` | //! | long | `i32` | //! | unsigned long | `u32` | //! | long long | `i64` | //! | unsigned long long | `u64` | //! | unrestricted float | `f32` | //! | float | `Finite` | //! | unrestricted double | `f64` | //! | double | `Finite` | //! | DOMString | `DOMString` | //! | USVString | `USVString` | //! | ByteString | `ByteString` | //! | object | `*mut JSObject` | //! | interface types | `&T` | `Root` | //! | dictionary types | `&T` | *unsupported* | //! | enumeration types | `T` | //! | callback function types | `Rc` | //! | nullable types | `Option` | //! | sequences | `Vec` | //! | union types | `T` | use core::nonzero::NonZero; use dom::bindings::error::throw_type_error; use dom::bindings::inheritance::Castable; use dom::bindings::js::Root; use dom::bindings::num::Finite; use dom::bindings::str::{ByteString, USVString}; use dom::bindings::utils::{DOMClass, Reflectable, Reflector}; use js; use js::glue::{GetProxyPrivate, IsWrapper, RUST_JS_NumberValue}; use js::glue::{RUST_JSID_IS_STRING, RUST_JSID_TO_STRING, UnwrapObject}; use js::jsapi::{HandleId, HandleObject, HandleValue, JS_GetClass}; use js::jsapi::{JSClass, JSContext, JSObject, JSString, MutableHandleValue}; use js::jsapi::{JS_GetLatin1StringCharsAndLength, JS_GetReservedSlot}; use js::jsapi::{JS_GetTwoByteStringCharsAndLength, JS_NewStringCopyN}; use js::jsapi::{JS_NewUCStringCopyN, JS_StringHasLatin1Chars, JS_WrapValue}; use js::jsval::{BooleanValue, Int32Value, NullValue, UInt32Value, UndefinedValue}; use js::jsval::{JSVal, ObjectOrNullValue, ObjectValue, StringValue}; use js::rust::{ToBoolean, ToNumber, ToString, ToUint16}; use js::rust::{ToInt32, ToUint32}; use js::rust::{ToInt64, ToUint64}; use libc; use num::Float; use num::traits::{Bounded, Zero}; use std::borrow::ToOwned; use std::mem; use std::rc::Rc; use std::{char, ptr, slice}; use util::str::DOMString; trait As: Copy { fn cast(self) -> O; } macro_rules! impl_as { ($I:ty, $O:ty) => ( impl As<$O> for $I { fn cast(self) -> $O { self as $O } } ) } impl_as!(f64, u8); impl_as!(f64, u16); impl_as!(f64, u32); impl_as!(f64, u64); impl_as!(f64, i8); impl_as!(f64, i16); impl_as!(f64, i32); impl_as!(f64, i64); impl_as!(u8, f64); impl_as!(u16, f64); impl_as!(u32, f64); impl_as!(u64, f64); impl_as!(i8, f64); impl_as!(i16, f64); impl_as!(i32, f64); impl_as!(i64, f64); impl_as!(i32, i8); impl_as!(i32, u8); impl_as!(i32, i16); impl_as!(u16, u16); impl_as!(i32, i32); impl_as!(u32, u32); impl_as!(i64, i64); impl_as!(u64, u64); /// A trait to check whether a given `JSObject` implements an IDL interface. pub trait IDLInterface { /// Returns whether the given DOM class derives that interface. fn derives(&'static DOMClass) -> bool; } /// A trait to mark an IDL interface as deriving from another one. #[rustc_on_unimplemented = "The IDL interface `{Self}` is not derived from `{T}`."] pub trait DerivedFrom: Castable {} /// A trait to convert Rust types to `JSVal`s. pub trait ToJSValConvertible { /// Convert `self` to a `JSVal`. JSAPI failure causes a task failure. fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue); } /// A trait to convert `JSVal`s to Rust types. pub trait FromJSValConvertible: Sized { /// Optional configurable behaviour switch; use () for no configuration. type Config; /// Convert `val` to type `Self`. /// Optional configuration of type `T` can be passed as the `option` /// argument. /// If it returns `Err(())`, a JSAPI exception is pending. fn from_jsval(cx: *mut JSContext, val: HandleValue, option: Self::Config) -> Result; } /// Behavior for converting out-of-range integers. #[derive(PartialEq, Eq)] pub enum ConversionBehavior { /// Wrap into the integer's range. Default, /// Throw an exception. EnforceRange, /// Clamp into the integer's range. Clamp } /// Try to cast the number to a smaller type, but /// if it doesn't fit, it will return an error. fn enforce_range(cx: *mut JSContext, d: f64) -> Result where D: Bounded + As, f64: As { if d.is_infinite() { throw_type_error(cx, "value out of range in an EnforceRange argument"); return Err(()); } let rounded = d.round(); if D::min_value().cast() <= rounded && rounded <= D::max_value().cast() { Ok(rounded.cast()) } else { throw_type_error(cx, "value out of range in an EnforceRange argument"); Err(()) } } /// Try to cast the number to a smaller type, but if it doesn't fit, /// round it to the MAX or MIN of the source type before casting it to /// the destination type. fn clamp_to(d: f64) -> D where D: Bounded + As + Zero, f64: As { if d.is_nan() { D::zero() } else if d > D::max_value().cast() { D::max_value() } else if d < D::min_value().cast() { D::min_value() } else { d.cast() } } //http://heycam.github.io/webidl/#es-void impl ToJSValConvertible for () { fn to_jsval(&self, _cx: *mut JSContext, rval: MutableHandleValue) { rval.set(UndefinedValue()); } } impl ToJSValConvertible for JSVal { fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) { rval.set(*self); if unsafe { !JS_WrapValue(cx, rval) } { panic!("JS_WrapValue failed."); } } } impl ToJSValConvertible for HandleValue { fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) { rval.set(self.get()); if unsafe { !JS_WrapValue(cx, rval) } { panic!("JS_WrapValue failed."); } } } #[inline] unsafe fn convert_int_from_jsval(cx: *mut JSContext, value: HandleValue, option: ConversionBehavior, convert_fn: unsafe fn(*mut JSContext, HandleValue) -> Result) -> Result where T: Bounded + Zero + As, M: Zero + As, f64: As { match option { ConversionBehavior::Default => Ok(try!(convert_fn(cx, value)).cast()), ConversionBehavior::EnforceRange => enforce_range(cx, try!(ToNumber(cx, value))), ConversionBehavior::Clamp => Ok(clamp_to(try!(ToNumber(cx, value)))), } } //http://heycam.github.io/webidl/#es-boolean impl ToJSValConvertible for bool { fn to_jsval(&self, _cx: *mut JSContext, rval: MutableHandleValue) { rval.set(BooleanValue(*self)); } } //http://heycam.github.io/webidl/#es-boolean impl FromJSValConvertible for bool { type Config = (); fn from_jsval(_cx: *mut JSContext, val: HandleValue, _option: ()) -> Result { Ok(unsafe { ToBoolean(val) }) } } //http://heycam.github.io/webidl/#es-byte impl ToJSValConvertible for i8 { fn to_jsval(&self, _cx: *mut JSContext, rval: MutableHandleValue) { rval.set(Int32Value(*self as i32)); } } //http://heycam.github.io/webidl/#es-byte impl FromJSValConvertible for i8 { type Config = ConversionBehavior; fn from_jsval(cx: *mut JSContext, val: HandleValue, option: ConversionBehavior) -> Result { unsafe { convert_int_from_jsval(cx, val, option, ToInt32) } } } //http://heycam.github.io/webidl/#es-octet impl ToJSValConvertible for u8 { fn to_jsval(&self, _cx: *mut JSContext, rval: MutableHandleValue) { rval.set(Int32Value(*self as i32)); } } //http://heycam.github.io/webidl/#es-octet impl FromJSValConvertible for u8 { type Config = ConversionBehavior; fn from_jsval(cx: *mut JSContext, val: HandleValue, option: ConversionBehavior) -> Result { unsafe { convert_int_from_jsval(cx, val, option, ToInt32) } } } //http://heycam.github.io/webidl/#es-short impl ToJSValConvertible for i16 { fn to_jsval(&self, _cx: *mut JSContext, rval: MutableHandleValue) { rval.set(Int32Value(*self as i32)); } } //http://heycam.github.io/webidl/#es-short impl FromJSValConvertible for i16 { type Config = ConversionBehavior; fn from_jsval(cx: *mut JSContext, val: HandleValue, option: ConversionBehavior) -> Result { unsafe { convert_int_from_jsval(cx, val, option, ToInt32) } } } //http://heycam.github.io/webidl/#es-unsigned-short impl ToJSValConvertible for u16 { fn to_jsval(&self, _cx: *mut JSContext, rval: MutableHandleValue) { rval.set(Int32Value(*self as i32)); } } //http://heycam.github.io/webidl/#es-unsigned-short impl FromJSValConvertible for u16 { type Config = ConversionBehavior; fn from_jsval(cx: *mut JSContext, val: HandleValue, option: ConversionBehavior) -> Result { unsafe { convert_int_from_jsval(cx, val, option, ToUint16) } } } //http://heycam.github.io/webidl/#es-long impl ToJSValConvertible for i32 { fn to_jsval(&self, _cx: *mut JSContext, rval: MutableHandleValue) { rval.set(Int32Value(*self)); } } //http://heycam.github.io/webidl/#es-long impl FromJSValConvertible for i32 { type Config = ConversionBehavior; fn from_jsval(cx: *mut JSContext, val: HandleValue, option: ConversionBehavior) -> Result { unsafe { convert_int_from_jsval(cx, val, option, ToInt32) } } } //http://heycam.github.io/webidl/#es-unsigned-long impl ToJSValConvertible for u32 { fn to_jsval(&self, _cx: *mut JSContext, rval: MutableHandleValue) { rval.set(UInt32Value(*self)); } } //http://heycam.github.io/webidl/#es-unsigned-long impl FromJSValConvertible for u32 { type Config = ConversionBehavior; fn from_jsval(cx: *mut JSContext, val: HandleValue, option: ConversionBehavior) -> Result { unsafe { convert_int_from_jsval(cx, val, option, ToUint32) } } } //http://heycam.github.io/webidl/#es-long-long impl ToJSValConvertible for i64 { fn to_jsval(&self, _cx: *mut JSContext, rval: MutableHandleValue) { unsafe { rval.set(RUST_JS_NumberValue(*self as f64)); } } } //http://heycam.github.io/webidl/#es-long-long impl FromJSValConvertible for i64 { type Config = ConversionBehavior; fn from_jsval(cx: *mut JSContext, val: HandleValue, option: ConversionBehavior) -> Result { unsafe { convert_int_from_jsval(cx, val, option, ToInt64) } } } //http://heycam.github.io/webidl/#es-unsigned-long-long impl ToJSValConvertible for u64 { fn to_jsval(&self, _cx: *mut JSContext, rval: MutableHandleValue) { unsafe { rval.set(RUST_JS_NumberValue(*self as f64)); } } } //http://heycam.github.io/webidl/#es-unsigned-long-long impl FromJSValConvertible for u64 { type Config = ConversionBehavior; fn from_jsval(cx: *mut JSContext, val: HandleValue, option: ConversionBehavior) -> Result { unsafe { convert_int_from_jsval(cx, val, option, ToUint64) } } } //http://heycam.github.io/webidl/#es-float impl ToJSValConvertible for f32 { fn to_jsval(&self, _cx: *mut JSContext, rval: MutableHandleValue) { unsafe { rval.set(RUST_JS_NumberValue(*self as f64)); } } } //http://heycam.github.io/webidl/#es-float impl FromJSValConvertible for f32 { type Config = (); fn from_jsval(cx: *mut JSContext, val: HandleValue, _option: ()) -> Result { let result = unsafe { ToNumber(cx, val) }; result.map(|f| f as f32) } } //http://heycam.github.io/webidl/#es-double impl ToJSValConvertible for f64 { fn to_jsval(&self, _cx: *mut JSContext, rval: MutableHandleValue) { unsafe { rval.set(RUST_JS_NumberValue(*self)); } } } //http://heycam.github.io/webidl/#es-double impl FromJSValConvertible for f64 { type Config = (); fn from_jsval(cx: *mut JSContext, val: HandleValue, _option: ()) -> Result { unsafe { ToNumber(cx, val) } } } impl ToJSValConvertible for Finite { #[inline] fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) { let value = **self; value.to_jsval(cx, rval); } } impl> FromJSValConvertible for Finite { type Config = (); fn from_jsval(cx: *mut JSContext, value: HandleValue, option: ()) -> Result, ()> { let result = try!(FromJSValConvertible::from_jsval(cx, value, option)); match Finite::new(result) { Some(v) => Ok(v), None => { throw_type_error(cx, "this argument is not a finite floating-point value"); Err(()) }, } } } impl ToJSValConvertible for str { fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) { let mut string_utf16: Vec = Vec::with_capacity(self.len()); unsafe { string_utf16.extend(self.utf16_units()); let jsstr = JS_NewUCStringCopyN(cx, string_utf16.as_ptr(), string_utf16.len() as libc::size_t); if jsstr.is_null() { panic!("JS_NewUCStringCopyN failed"); } rval.set(StringValue(&*jsstr)); } } } //http://heycam.github.io/webidl/#es-DOMString impl ToJSValConvertible for DOMString { fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) { (**self).to_jsval(cx, rval); } } /// Behavior for stringification of `JSVal`s. #[derive(PartialEq)] pub enum StringificationBehavior { /// Convert `null` to the string `"null"`. Default, /// Convert `null` to the empty string. Empty, } /// Convert the given `JSString` to a `DOMString`. Fails if the string does not /// contain valid UTF-16. pub fn jsstring_to_str(cx: *mut JSContext, s: *mut JSString) -> DOMString { let mut length = 0; let latin1 = unsafe { JS_StringHasLatin1Chars(s) }; if latin1 { let chars = unsafe { JS_GetLatin1StringCharsAndLength(cx, ptr::null(), s, &mut length) }; assert!(!chars.is_null()); let mut buf = String::with_capacity(length as usize); for i in 0..(length as isize) { unsafe { buf.push(*chars.offset(i) as char); } } buf } else { let chars = unsafe { JS_GetTwoByteStringCharsAndLength(cx, ptr::null(), s, &mut length) }; assert!(!chars.is_null()); let potentially_ill_formed_utf16 = unsafe { slice::from_raw_parts(chars as *const u16, length as usize) }; let mut s = String::with_capacity(length as usize); for item in char::decode_utf16(potentially_ill_formed_utf16.iter().cloned()) { match item { Ok(c) => s.push(c), Err(_) => { // FIXME: Add more info like document URL in the message? macro_rules! message { () => { "Found an unpaired surrogate in a DOM string. \ If you see this in real web content, \ please comment on https://github.com/servo/servo/issues/6564" } } if ::util::opts::get().replace_surrogates { error!(message!()); s.push('\u{FFFD}'); } else { panic!(concat!(message!(), " Use `-Z replace-surrogates` \ on the command line to make this non-fatal.")); } } } } s } } /// Convert the given `jsid` to a `DOMString`. Fails if the `jsid` is not a /// string, or if the string does not contain valid UTF-16. pub fn jsid_to_str(cx: *mut JSContext, id: HandleId) -> DOMString { unsafe { assert!(RUST_JSID_IS_STRING(id)); jsstring_to_str(cx, RUST_JSID_TO_STRING(id)) } } //http://heycam.github.io/webidl/#es-DOMString impl FromJSValConvertible for DOMString { type Config = StringificationBehavior; fn from_jsval(cx: *mut JSContext, value: HandleValue, null_behavior: StringificationBehavior) -> Result { if null_behavior == StringificationBehavior::Empty && value.get().is_null() { Ok("".to_owned()) } else { let jsstr = unsafe { ToString(cx, value) }; if jsstr.is_null() { debug!("ToString failed"); Err(()) } else { Ok(jsstring_to_str(cx, jsstr)) } } } } //http://heycam.github.io/webidl/#es-USVString impl ToJSValConvertible for USVString { fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) { self.0.to_jsval(cx, rval); } } //http://heycam.github.io/webidl/#es-USVString impl FromJSValConvertible for USVString { type Config = (); fn from_jsval(cx: *mut JSContext, value: HandleValue, _: ()) -> Result { let jsstr = unsafe { ToString(cx, value) }; if jsstr.is_null() { debug!("ToString failed"); return Err(()); } let latin1 = unsafe { JS_StringHasLatin1Chars(jsstr) }; if latin1 { return Ok(USVString(jsstring_to_str(cx, jsstr))); } unsafe { let mut length = 0; let chars = JS_GetTwoByteStringCharsAndLength(cx, ptr::null(), jsstr, &mut length); assert!(!chars.is_null()); let char_vec = slice::from_raw_parts(chars as *const u16, length as usize); Ok(USVString(String::from_utf16_lossy(char_vec))) } } } //http://heycam.github.io/webidl/#es-ByteString impl ToJSValConvertible for ByteString { fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) { unsafe { let jsstr = JS_NewStringCopyN(cx, self.as_ptr() as *const libc::c_char, self.len() as libc::size_t); if jsstr.is_null() { panic!("JS_NewStringCopyN failed"); } rval.set(StringValue(&*jsstr)); } } } //http://heycam.github.io/webidl/#es-ByteString impl FromJSValConvertible for ByteString { type Config = (); fn from_jsval(cx: *mut JSContext, value: HandleValue, _option: ()) -> Result { let string = unsafe { ToString(cx, value) }; if string.is_null() { debug!("ToString failed"); return Err(()); } let latin1 = unsafe { JS_StringHasLatin1Chars(string) }; if latin1 { let mut length = 0; let chars = unsafe { JS_GetLatin1StringCharsAndLength(cx, ptr::null(), string, &mut length) }; assert!(!chars.is_null()); let char_slice = unsafe { slice::from_raw_parts(chars as *mut u8, length as usize) }; return Ok(ByteString::new(char_slice.to_vec())); } unsafe { let mut length = 0; let chars = JS_GetTwoByteStringCharsAndLength(cx, ptr::null(), string, &mut length); let char_vec = slice::from_raw_parts(chars, length as usize); if char_vec.iter().any(|&c| c > 0xFF) { throw_type_error(cx, "Invalid ByteString"); Err(()) } else { Ok(ByteString::new(char_vec.iter().map(|&c| c as u8).collect())) } } } } impl ToJSValConvertible for Reflector { fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) { let obj = self.get_jsobject().get(); assert!(!obj.is_null()); rval.set(ObjectValue(unsafe { &*obj })); if unsafe { !JS_WrapValue(cx, rval) } { panic!("JS_WrapValue failed."); } } } /// Returns whether the given `clasp` is one for a DOM object. pub fn is_dom_class(clasp: *const JSClass) -> bool { unsafe { ((*clasp).flags & js::JSCLASS_IS_DOMJSCLASS) != 0 } } /// Returns whether `obj` is a DOM object implemented as a proxy. pub fn is_dom_proxy(obj: *mut JSObject) -> bool { use js::glue::IsProxyHandlerFamily; unsafe { let clasp = JS_GetClass(obj); ((*clasp).flags & js::JSCLASS_IS_PROXY) != 0 && IsProxyHandlerFamily(obj) != 0 } } /// The index of the slot wherein a pointer to the reflected DOM object is /// stored for non-proxy bindings. // We use slot 0 for holding the raw object. This is safe for both // globals and non-globals. pub const DOM_OBJECT_SLOT: u32 = 0; /// Get the private pointer of a DOM object from a given reflector. unsafe fn private_from_reflector(obj: *mut JSObject) -> *const libc::c_void { let clasp = JS_GetClass(obj); let value = if is_dom_class(clasp) { JS_GetReservedSlot(obj, DOM_OBJECT_SLOT) } else { debug_assert!(is_dom_proxy(obj)); GetProxyPrivate(obj) }; if value.is_undefined() { ptr::null() } else { value.to_private() } } /// Get the DOM object from the given reflector. pub unsafe fn native_from_reflector(obj: *mut JSObject) -> *const T { private_from_reflector(obj) as *const T } /// Get the `DOMClass` from `obj`, or `Err(())` if `obj` is not a DOM object. pub unsafe fn get_dom_class(obj: *mut JSObject) -> Result<&'static DOMClass, ()> { use dom::bindings::utils::DOMJSClass; use js::glue::GetProxyHandlerExtra; let clasp = JS_GetClass(obj); if is_dom_class(&*clasp) { debug!("plain old dom object"); let domjsclass: *const DOMJSClass = clasp as *const DOMJSClass; return Ok(&(&*domjsclass).dom_class); } if is_dom_proxy(obj) { debug!("proxy dom object"); let dom_class: *const DOMClass = GetProxyHandlerExtra(obj) as *const DOMClass; return Ok(&*dom_class); } debug!("not a dom object"); Err(()) } /// Get a `*const libc::c_void` for the given DOM object, unwrapping any /// wrapper around it first, and checking if the object is of the correct type. /// /// Returns Err(()) if `obj` is an opaque security wrapper or if the object is /// not an object for a DOM object of the given type (as defined by the /// proto_id and proto_depth). #[inline] pub unsafe fn private_from_proto_check(mut obj: *mut JSObject, proto_check: F) -> Result<*const libc::c_void, ()> where F: Fn(&'static DOMClass) -> bool { let dom_class = try!(get_dom_class(obj).or_else(|_| { if IsWrapper(obj) { debug!("found wrapper"); obj = UnwrapObject(obj, /* stopAtOuter = */ 0); if obj.is_null() { debug!("unwrapping security wrapper failed"); Err(()) } else { assert!(!IsWrapper(obj)); debug!("unwrapped successfully"); get_dom_class(obj) } } else { debug!("not a dom wrapper"); Err(()) } })); if proto_check(dom_class) { debug!("good prototype"); Ok(private_from_reflector(obj)) } else { debug!("bad prototype"); Err(()) } } /// Get a `Root` for the given DOM object, unwrapping any wrapper /// around it first, and checking if the object is of the correct type. /// /// Returns Err(()) if `obj` is an opaque security wrapper or if the object is /// not a reflector for a DOM object of the given type (as defined by the /// proto_id and proto_depth). pub fn native_from_reflector_jsmanaged(obj: *mut JSObject) -> Result, ()> where T: Reflectable + IDLInterface { unsafe { private_from_proto_check(obj, T::derives).map(|obj| { Root::new(NonZero::new(obj as *const T)) }) } } /// Get a Rooted for a DOM object accessible from a HandleValue pub fn native_from_handlevalue(v: HandleValue) -> Result, ()> where T: Reflectable + IDLInterface { native_from_reflector_jsmanaged(v.get().to_object()) } /// Get a Rooted for a DOM object accessible from a HandleObject pub fn native_from_handleobject(obj: HandleObject) -> Result, ()> where T: Reflectable + IDLInterface { native_from_reflector_jsmanaged(obj.get()) } impl ToJSValConvertible for Root { fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) { self.r().reflector().to_jsval(cx, rval); } } impl<'a, T: Reflectable> ToJSValConvertible for &'a T { fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) { self.reflector().to_jsval(cx, rval); } } impl ToJSValConvertible for Option { fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) { match self { &Some(ref value) => value.to_jsval(cx, rval), &None => rval.set(NullValue()), } } } impl ToJSValConvertible for Option> { fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) { match self { &Some(ref value) => (**value).to_jsval(cx, rval), &None => rval.set(NullValue()), } } } impl FromJSValConvertible for Option { type Config = T::Config; fn from_jsval(cx: *mut JSContext, value: HandleValue, option: T::Config) -> Result, ()> { if value.get().is_null_or_undefined() { Ok(None) } else { let result: Result = FromJSValConvertible::from_jsval(cx, value, option); result.map(Some) } } } //http://heycam.github.io/webidl/#es-object impl ToJSValConvertible for *mut JSObject { fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) { rval.set(ObjectOrNullValue(*self)); unsafe { assert!(JS_WrapValue(cx, rval)); } } }