use std::{ffi, ptr};
pub(crate) use js::conversions::{
ConversionBehavior, ConversionResult, FromJSValConvertible, ToJSValConvertible,
};
use js::error::throw_type_error;
use js::glue::{GetProxyReservedSlot, IsWrapper, JS_GetReservedSlot, UnwrapObjectDynamic};
use js::jsapi::{Heap, IsWindowProxy, JSContext, JSObject, JS_IsExceptionPending};
use js::jsval::UndefinedValue;
use js::rust::wrappers::{IsArrayObject, JS_GetProperty, JS_HasProperty};
use js::rust::{is_dom_object, HandleId, HandleObject, HandleValue, MutableHandleValue};
use num_traits::Float;
pub(crate) use script_bindings::conversions::*;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::num::Finite;
use crate::dom::bindings::reflector::DomObject;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::bindings::trace::{JSTraceable, RootedTraceableBox};
use crate::dom::bindings::utils::DOMClass;
use crate::dom::filelist::FileList;
use crate::dom::htmlcollection::HTMLCollection;
use crate::dom::htmlformcontrolscollection::HTMLFormControlsCollection;
use crate::dom::htmloptionscollection::HTMLOptionsCollection;
use crate::dom::nodelist::NodeList;
use crate::dom::windowproxy::WindowProxy;
impl<T: Float + ToJSValConvertible> ToJSValConvertible for Finite<T> {
#[inline]
unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
let value = **self;
value.to_jsval(cx, rval);
}
}
impl<T: Float + FromJSValConvertible<Config = ()>> FromJSValConvertible for Finite<T> {
type Config = ();
unsafe fn from_jsval(
cx: *mut JSContext,
value: HandleValue,
option: (),
) -> Result<ConversionResult<Finite<T>>, ()> {
let result = match FromJSValConvertible::from_jsval(cx, value, option)? {
ConversionResult::Success(v) => v,
ConversionResult::Failure(error) => {
throw_type_error(cx, &error);
return Err(());
},
};
match Finite::new(result) {
Some(v) => Ok(ConversionResult::Success(v)),
None => {
throw_type_error(cx, "this argument is not a finite floating-point value");
Err(())
},
}
}
}
impl<T: DomObject + IDLInterface> FromJSValConvertible for DomRoot<T> {
type Config = ();
unsafe fn from_jsval(
cx: *mut JSContext,
value: HandleValue,
_config: Self::Config,
) -> Result<ConversionResult<DomRoot<T>>, ()> {
Ok(match root_from_handlevalue(value, cx) {
Ok(result) => ConversionResult::Success(result),
Err(()) => ConversionResult::Failure("value is not an object".into()),
})
}
}
impl<T: ToJSValConvertible + JSTraceable> ToJSValConvertible for RootedTraceableBox<T> {
#[inline]
unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
let value = &**self;
value.to_jsval(cx, rval);
}
}
impl<T> FromJSValConvertible for RootedTraceableBox<Heap<T>>
where
T: FromJSValConvertible + js::rust::GCMethods + Copy,
Heap<T>: JSTraceable + Default,
{
type Config = T::Config;
unsafe fn from_jsval(
cx: *mut JSContext,
value: HandleValue,
config: Self::Config,
) -> Result<ConversionResult<Self>, ()> {
T::from_jsval(cx, value, config).map(|result| match result {
ConversionResult::Success(inner) => {
ConversionResult::Success(RootedTraceableBox::from_box(Heap::boxed(inner)))
},
ConversionResult::Failure(msg) => ConversionResult::Failure(msg),
})
}
}
pub(crate) unsafe fn jsid_to_string(cx: *mut JSContext, id: HandleId) -> Option<DOMString> {
let id_raw = *id;
if id_raw.is_string() {
let jsstr = std::ptr::NonNull::new(id_raw.to_string()).unwrap();
return Some(jsstring_to_str(cx, jsstr));
}
if id_raw.is_int() {
return Some(id_raw.to_int().to_string().into());
}
None
}
pub(crate) use script_bindings::conversions::is_dom_proxy;
pub(crate) const DOM_OBJECT_SLOT: u32 = 0;
pub(crate) unsafe fn private_from_object(obj: *mut JSObject) -> *const libc::c_void {
let mut value = UndefinedValue();
if is_dom_object(obj) {
JS_GetReservedSlot(obj, DOM_OBJECT_SLOT, &mut value);
} else {
debug_assert!(is_dom_proxy(obj));
GetProxyReservedSlot(obj, 0, &mut value);
};
if value.is_undefined() {
ptr::null()
} else {
value.to_private()
}
}
pub(crate) enum PrototypeCheck {
Derive(fn(&'static DOMClass) -> bool),
Depth { depth: usize, proto_id: u16 },
}
#[inline]
pub(crate) unsafe fn private_from_proto_check(
mut obj: *mut JSObject,
cx: *mut JSContext,
proto_check: PrototypeCheck,
) -> Result<*const libc::c_void, ()> {
let dom_class = get_dom_class(obj).or_else(|_| {
if IsWrapper(obj) {
trace!("found wrapper");
obj = UnwrapObjectDynamic(obj, cx, false);
if obj.is_null() {
trace!("unwrapping security wrapper failed");
Err(())
} else {
assert!(!IsWrapper(obj));
trace!("unwrapped successfully");
get_dom_class(obj)
}
} else {
trace!("not a dom wrapper");
Err(())
}
})?;
let prototype_matches = match proto_check {
PrototypeCheck::Derive(f) => (f)(dom_class),
PrototypeCheck::Depth { depth, proto_id } => {
dom_class.interface_chain[depth] as u16 == proto_id
},
};
if prototype_matches {
trace!("good prototype");
Ok(private_from_object(obj))
} else {
trace!("bad prototype");
Err(())
}
}
#[inline]
unsafe fn private_from_proto_check_static(
obj: *mut JSObject,
proto_check: fn(&'static DOMClass) -> bool,
) -> Result<*const libc::c_void, ()> {
let dom_class = get_dom_class(obj).map_err(|_| ())?;
if proto_check(dom_class) {
trace!("good prototype");
Ok(private_from_object(obj))
} else {
trace!("bad prototype");
Err(())
}
}
pub(crate) fn native_from_object<T>(obj: *mut JSObject, cx: *mut JSContext) -> Result<*const T, ()>
where
T: DomObject + IDLInterface,
{
unsafe {
private_from_proto_check(obj, cx, PrototypeCheck::Derive(T::derives))
.map(|ptr| ptr as *const T)
}
}
pub(crate) fn native_from_object_static<T>(obj: *mut JSObject) -> Result<*const T, ()>
where
T: DomObject + IDLInterface,
{
unsafe { private_from_proto_check_static(obj, T::derives).map(|ptr| ptr as *const T) }
}
pub(crate) fn root_from_object<T>(obj: *mut JSObject, cx: *mut JSContext) -> Result<DomRoot<T>, ()>
where
T: DomObject + IDLInterface,
{
native_from_object(obj, cx).map(|ptr| unsafe { DomRoot::from_ref(&*ptr) })
}
pub(crate) fn root_from_object_static<T>(obj: *mut JSObject) -> Result<DomRoot<T>, ()>
where
T: DomObject + IDLInterface,
{
native_from_object_static(obj).map(|ptr| unsafe { DomRoot::from_ref(&*ptr) })
}
pub(crate) fn native_from_handlevalue<T>(v: HandleValue, cx: *mut JSContext) -> Result<*const T, ()>
where
T: DomObject + IDLInterface,
{
if !v.get().is_object() {
return Err(());
}
native_from_object(v.get().to_object(), cx)
}
pub(crate) fn root_from_handlevalue<T>(v: HandleValue, cx: *mut JSContext) -> Result<DomRoot<T>, ()>
where
T: DomObject + IDLInterface,
{
if !v.get().is_object() {
return Err(());
}
root_from_object(v.get().to_object(), cx)
}
pub(crate) fn root_from_handleobject<T>(
obj: HandleObject,
cx: *mut JSContext,
) -> Result<DomRoot<T>, ()>
where
T: DomObject + IDLInterface,
{
root_from_object(obj.get(), cx)
}
impl<T: DomObject> ToJSValConvertible for DomRoot<T> {
unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
self.reflector().to_jsval(cx, rval);
}
}
pub(crate) unsafe fn is_array_like(cx: *mut JSContext, value: HandleValue) -> bool {
let mut is_array = false;
assert!(IsArrayObject(cx, value, &mut is_array));
if is_array {
return true;
}
let object: *mut JSObject = match FromJSValConvertible::from_jsval(cx, value, ()).unwrap() {
ConversionResult::Success(object) => object,
_ => return false,
};
if root_from_object::<FileList>(object, cx).is_ok() {
return true;
}
if root_from_object::<HTMLCollection>(object, cx).is_ok() {
return true;
}
if root_from_object::<HTMLFormControlsCollection>(object, cx).is_ok() {
return true;
}
if root_from_object::<HTMLOptionsCollection>(object, cx).is_ok() {
return true;
}
if root_from_object::<NodeList>(object, cx).is_ok() {
return true;
}
false
}
pub(crate) unsafe fn get_property_jsval(
cx: *mut JSContext,
object: HandleObject,
name: &str,
mut rval: MutableHandleValue,
) -> Fallible<()> {
rval.set(UndefinedValue());
let cname = match ffi::CString::new(name) {
Ok(cname) => cname,
Err(_) => return Ok(()),
};
let mut found = false;
if JS_HasProperty(cx, object, cname.as_ptr(), &mut found) && found {
JS_GetProperty(cx, object, cname.as_ptr(), rval);
if JS_IsExceptionPending(cx) {
return Err(Error::JSFailed);
}
Ok(())
} else if JS_IsExceptionPending(cx) {
Err(Error::JSFailed)
} else {
Ok(())
}
}
pub(crate) unsafe fn get_property<T>(
cx: *mut JSContext,
object: HandleObject,
name: &str,
option: T::Config,
) -> Fallible<Option<T>>
where
T: FromJSValConvertible,
{
debug!("Getting property {}.", name);
rooted!(in(cx) let mut result = UndefinedValue());
get_property_jsval(cx, object, name, result.handle_mut())?;
if result.is_undefined() {
debug!("No property {}.", name);
return Ok(None);
}
debug!("Converting property {}.", name);
match T::from_jsval(cx, result.handle(), option) {
Ok(ConversionResult::Success(value)) => Ok(Some(value)),
Ok(ConversionResult::Failure(_)) => Ok(None),
Err(()) => Err(Error::JSFailed),
}
}
pub(crate) unsafe fn windowproxy_from_handlevalue(
v: HandleValue,
_cx: *mut JSContext,
) -> Result<DomRoot<WindowProxy>, ()> {
if !v.get().is_object() {
return Err(());
}
let object = v.get().to_object();
if !IsWindowProxy(object) {
return Err(());
}
let mut value = UndefinedValue();
GetProxyReservedSlot(object, 0, &mut value);
let ptr = value.to_private() as *const WindowProxy;
Ok(DomRoot::from_ref(&*ptr))
}