/* 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/. */ #![deny(missing_docs)] //! Conversions of Rust values to and from `JSVal`. use dom::bindings::codegen::PrototypeList; use dom::bindings::js::{JS, JSRef, Root}; use dom::bindings::str::ByteString; use dom::bindings::utils::{Reflectable, Reflector, DOMClass}; use servo_util::str::DOMString; use js; use js::glue::{RUST_JSID_TO_STRING, RUST_JSID_IS_STRING}; use js::glue::RUST_JS_NumberValue; use js::jsapi::{JSBool, JSContext, JSObject, JSString, jsid}; use js::jsapi::{JS_ValueToUint64, JS_ValueToInt64}; use js::jsapi::{JS_ValueToECMAUint32, JS_ValueToECMAInt32}; use js::jsapi::{JS_ValueToUint16, JS_ValueToNumber, JS_ValueToBoolean}; use js::jsapi::{JS_ValueToString, JS_GetStringCharsAndLength}; use js::jsapi::{JS_NewUCStringCopyN, JS_NewStringCopyN}; use js::jsapi::{JS_WrapValue}; use js::jsapi::{JSClass, JS_GetClass}; use js::jsval::JSVal; use js::jsval::{UndefinedValue, NullValue, BooleanValue, Int32Value, UInt32Value}; use js::jsval::{StringValue, ObjectValue, ObjectOrNullValue}; use libc; use std::default; use std::slice; /// A trait to retrieve the constants necessary to check if a `JSObject` /// implements a given interface. // FIXME (https://github.com/rust-lang/rfcs/pull/4) // remove Option arguments. pub trait IDLInterface { /// Returns the prototype ID. fn get_prototype_id(_: Option) -> PrototypeList::ID; /// Returns the prototype depth, i.e., the number of interfaces this /// interface inherits from. fn get_prototype_depth(_: Option) -> uint; } /// 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) -> JSVal; } /// A trait to convert `JSVal`s to Rust types. pub trait FromJSValConvertible { /// 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: JSVal, option: T) -> Result; } impl ToJSValConvertible for () { fn to_jsval(&self, _cx: *mut JSContext) -> JSVal { UndefinedValue() } } impl ToJSValConvertible for JSVal { fn to_jsval(&self, cx: *mut JSContext) -> JSVal { let mut value = *self; if unsafe { JS_WrapValue(cx, &mut value) } == 0 { panic!("JS_WrapValue failed."); } value } } unsafe fn convert_from_jsval( cx: *mut JSContext, value: JSVal, convert_fn: unsafe extern "C" fn(*mut JSContext, JSVal, *mut T) -> JSBool) -> Result { let mut ret = default::Default::default(); if convert_fn(cx, value, &mut ret) == 0 { Err(()) } else { Ok(ret) } } impl ToJSValConvertible for bool { fn to_jsval(&self, _cx: *mut JSContext) -> JSVal { BooleanValue(*self) } } impl FromJSValConvertible<()> for bool { fn from_jsval(cx: *mut JSContext, val: JSVal, _option: ()) -> Result { let result = unsafe { convert_from_jsval(cx, val, JS_ValueToBoolean) }; result.map(|b| b != 0) } } impl ToJSValConvertible for i8 { fn to_jsval(&self, _cx: *mut JSContext) -> JSVal { Int32Value(*self as i32) } } impl FromJSValConvertible<()> for i8 { fn from_jsval(cx: *mut JSContext, val: JSVal, _option: ()) -> Result { let result = unsafe { convert_from_jsval(cx, val, JS_ValueToECMAInt32) }; result.map(|v| v as i8) } } impl ToJSValConvertible for u8 { fn to_jsval(&self, _cx: *mut JSContext) -> JSVal { Int32Value(*self as i32) } } impl FromJSValConvertible<()> for u8 { fn from_jsval(cx: *mut JSContext, val: JSVal, _option: ()) -> Result { let result = unsafe { convert_from_jsval(cx, val, JS_ValueToECMAInt32) }; result.map(|v| v as u8) } } impl ToJSValConvertible for i16 { fn to_jsval(&self, _cx: *mut JSContext) -> JSVal { Int32Value(*self as i32) } } impl FromJSValConvertible<()> for i16 { fn from_jsval(cx: *mut JSContext, val: JSVal, _option: ()) -> Result { let result = unsafe { convert_from_jsval(cx, val, JS_ValueToECMAInt32) }; result.map(|v| v as i16) } } impl ToJSValConvertible for u16 { fn to_jsval(&self, _cx: *mut JSContext) -> JSVal { Int32Value(*self as i32) } } impl FromJSValConvertible<()> for u16 { fn from_jsval(cx: *mut JSContext, val: JSVal, _option: ()) -> Result { unsafe { convert_from_jsval(cx, val, JS_ValueToUint16) } } } impl ToJSValConvertible for i32 { fn to_jsval(&self, _cx: *mut JSContext) -> JSVal { Int32Value(*self) } } impl FromJSValConvertible<()> for i32 { fn from_jsval(cx: *mut JSContext, val: JSVal, _option: ()) -> Result { unsafe { convert_from_jsval(cx, val, JS_ValueToECMAInt32) } } } impl ToJSValConvertible for u32 { fn to_jsval(&self, _cx: *mut JSContext) -> JSVal { UInt32Value(*self) } } impl FromJSValConvertible<()> for u32 { fn from_jsval(cx: *mut JSContext, val: JSVal, _option: ()) -> Result { unsafe { convert_from_jsval(cx, val, JS_ValueToECMAUint32) } } } impl ToJSValConvertible for i64 { fn to_jsval(&self, _cx: *mut JSContext) -> JSVal { unsafe { RUST_JS_NumberValue(*self as f64) } } } impl FromJSValConvertible<()> for i64 { fn from_jsval(cx: *mut JSContext, val: JSVal, _option: ()) -> Result { unsafe { convert_from_jsval(cx, val, JS_ValueToInt64) } } } impl ToJSValConvertible for u64 { fn to_jsval(&self, _cx: *mut JSContext) -> JSVal { unsafe { RUST_JS_NumberValue(*self as f64) } } } impl FromJSValConvertible<()> for u64 { fn from_jsval(cx: *mut JSContext, val: JSVal, _option: ()) -> Result { unsafe { convert_from_jsval(cx, val, JS_ValueToUint64) } } } impl ToJSValConvertible for f32 { fn to_jsval(&self, _cx: *mut JSContext) -> JSVal { unsafe { RUST_JS_NumberValue(*self as f64) } } } impl FromJSValConvertible<()> for f32 { fn from_jsval(cx: *mut JSContext, val: JSVal, _option: ()) -> Result { let result = unsafe { convert_from_jsval(cx, val, JS_ValueToNumber) }; result.map(|f| f as f32) } } impl ToJSValConvertible for f64 { fn to_jsval(&self, _cx: *mut JSContext) -> JSVal { unsafe { RUST_JS_NumberValue(*self) } } } impl FromJSValConvertible<()> for f64 { fn from_jsval(cx: *mut JSContext, val: JSVal, _option: ()) -> Result { unsafe { convert_from_jsval(cx, val, JS_ValueToNumber) } } } impl ToJSValConvertible for DOMString { fn to_jsval(&self, cx: *mut JSContext) -> JSVal { unsafe { let string_utf16: Vec = self.as_slice().utf16_units().collect(); let jsstr = JS_NewUCStringCopyN(cx, string_utf16.as_ptr(), string_utf16.len() as libc::size_t); if jsstr.is_null() { panic!("JS_NewUCStringCopyN failed"); } StringValue(&*jsstr) } } } /// Behavior for stringification of `JSVal`s. #[deriving(PartialEq)] pub enum StringificationBehavior { /// Convert `null` to the string `"null"`. Default, /// Convert `null` to the empty string. Empty, } impl default::Default for StringificationBehavior { fn default() -> StringificationBehavior { StringificationBehavior::Default } } /// 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 { unsafe { let mut length = 0; let chars = JS_GetStringCharsAndLength(cx, s, &mut length); slice::raw::buf_as_slice(chars, length as uint, |char_vec| { String::from_utf16(char_vec).unwrap() }) } } /// 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: jsid) -> DOMString { unsafe { assert!(RUST_JSID_IS_STRING(id) != 0); jsstring_to_str(cx, RUST_JSID_TO_STRING(id)) } } impl FromJSValConvertible for DOMString { fn from_jsval(cx: *mut JSContext, value: JSVal, nullBehavior: StringificationBehavior) -> Result { if nullBehavior == StringificationBehavior::Empty && value.is_null() { Ok("".into_string()) } else { let jsstr = unsafe { JS_ValueToString(cx, value) }; if jsstr.is_null() { debug!("JS_ValueToString failed"); Err(()) } else { Ok(jsstring_to_str(cx, jsstr)) } } } } impl ToJSValConvertible for ByteString { fn to_jsval(&self, cx: *mut JSContext) -> JSVal { unsafe { let slice = self.as_slice(); let jsstr = JS_NewStringCopyN(cx, slice.as_ptr() as *const libc::c_char, slice.len() as libc::size_t); if jsstr.is_null() { panic!("JS_NewStringCopyN failed"); } StringValue(&*jsstr) } } } impl FromJSValConvertible<()> for ByteString { fn from_jsval(cx: *mut JSContext, value: JSVal, _option: ()) -> Result { unsafe { let string = JS_ValueToString(cx, value); if string.is_null() { debug!("JS_ValueToString failed"); return Err(()); } let mut length = 0; let chars = JS_GetStringCharsAndLength(cx, string, &mut length); slice::raw::buf_as_slice(chars, length as uint, |char_vec| { if char_vec.iter().any(|&c| c > 0xFF) { // XXX Throw 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) -> JSVal { let obj = self.get_jsobject(); assert!(obj.is_not_null()); let mut value = ObjectValue(unsafe { &*obj }); if unsafe { JS_WrapValue(cx, &mut value) } == 0 { panic!("JS_WrapValue failed."); } value } } /// Returns whether the given `clasp` is one for a DOM object. 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::{js_IsObjectProxyClass, js_IsFunctionProxyClass, IsProxyHandlerFamily}; unsafe { (js_IsObjectProxyClass(obj) || js_IsFunctionProxyClass(obj)) && IsProxyHandlerFamily(obj) } } /// 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: uint = 0; const DOM_PROXY_OBJECT_SLOT: uint = js::JSSLOT_PROXY_PRIVATE as uint; /// Returns the index of the slot wherein a pointer to the reflected DOM object /// is stored. /// /// Fails if `obj` is not a DOM object. pub unsafe fn dom_object_slot(obj: *mut JSObject) -> u32 { let clasp = JS_GetClass(obj); if is_dom_class(&*clasp) { DOM_OBJECT_SLOT as u32 } else { assert!(is_dom_proxy(obj)); DOM_PROXY_OBJECT_SLOT as u32 } } /// Get the DOM object from the given reflector. pub unsafe fn unwrap(obj: *mut JSObject) -> *const T { use js::jsapi::JS_GetReservedSlot; let slot = dom_object_slot(obj); let value = JS_GetReservedSlot(obj, slot); value.to_private() as *const T } /// Get the `DOMClass` from `obj`, or `Err(())` if `obj` is not a DOM object. unsafe fn get_dom_class(obj: *mut JSObject) -> Result { 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"); return Err(()); } /// Get a `JS` 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 unwrap_jsmanaged(mut obj: *mut JSObject) -> Result, ()> where T: Reflectable + IDLInterface { use js::glue::{IsWrapper, UnwrapObject}; use std::ptr; unsafe { let dom_class = try!(get_dom_class(obj).or_else(|_| { if IsWrapper(obj) == 1 { debug!("found wrapper"); obj = UnwrapObject(obj, /* stopAtOuter = */ 0, ptr::null_mut()); if obj.is_null() { debug!("unwrapping security wrapper failed"); Err(()) } else { assert!(IsWrapper(obj) == 0); debug!("unwrapped successfully"); get_dom_class(obj) } } else { debug!("not a dom wrapper"); Err(()) } })); let proto_id = IDLInterface::get_prototype_id(None::); let proto_depth = IDLInterface::get_prototype_depth(None::); if dom_class.interface_chain[proto_depth] == proto_id { debug!("good prototype"); Ok(JS::from_raw(unwrap(obj))) } else { debug!("bad prototype"); Err(()) } } } impl FromJSValConvertible<()> for JS { fn from_jsval(_cx: *mut JSContext, value: JSVal, _option: ()) -> Result, ()> { if !value.is_object() { return Err(()); } unwrap_jsmanaged(value.to_object()) } } impl<'a, 'b, T: Reflectable> ToJSValConvertible for Root<'a, 'b, T> { fn to_jsval(&self, cx: *mut JSContext) -> JSVal { self.reflector().to_jsval(cx) } } impl<'a, T: Reflectable> ToJSValConvertible for JSRef<'a, T> { fn to_jsval(&self, cx: *mut JSContext) -> JSVal { self.reflector().to_jsval(cx) } } impl<'a, T: Reflectable> ToJSValConvertible for JS { fn to_jsval(&self, cx: *mut JSContext) -> JSVal { self.reflector().to_jsval(cx) } } impl ToJSValConvertible for Option { fn to_jsval(&self, cx: *mut JSContext) -> JSVal { match self { &Some(ref value) => value.to_jsval(cx), &None => NullValue(), } } } impl> FromJSValConvertible<()> for Option { fn from_jsval(cx: *mut JSContext, value: JSVal, _: ()) -> Result, ()> { if value.is_null_or_undefined() { Ok(None) } else { let option: X = default::Default::default(); let result: Result = FromJSValConvertible::from_jsval(cx, value, option); result.map(Some) } } } impl ToJSValConvertible for *mut JSObject { fn to_jsval(&self, cx: *mut JSContext) -> JSVal { let mut wrapped = ObjectOrNullValue(*self); unsafe { assert!(JS_WrapValue(cx, &mut wrapped) != 0); } wrapped } }