/* 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 https://mozilla.org/MPL/2.0/. */ //! Various utilities to glue JavaScript and the DOM implementation together. use crate::dom::bindings::codegen::InterfaceObjectMap; use crate::dom::bindings::codegen::PrototypeList; use crate::dom::bindings::codegen::PrototypeList::{MAX_PROTO_CHAIN_LENGTH, PROTO_OR_IFACE_LENGTH}; use crate::dom::bindings::conversions::{jsstring_to_str, private_from_proto_check}; use crate::dom::bindings::error::throw_invalid_this; use crate::dom::bindings::inheritance::TopTypeId; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::str::DOMString; use crate::dom::bindings::trace::trace_object; use crate::dom::messageport::MessagePort; use crate::dom::windowproxy; use crate::script_runtime::JSContext as SafeJSContext; use js::conversions::ToJSValConvertible; use js::glue::{CallJitGetterOp, CallJitMethodOp, CallJitSetterOp, IsWrapper}; use js::glue::{GetCrossCompartmentWrapper, JS_GetReservedSlot, WrapperNew}; use js::glue::{UnwrapObjectDynamic, RUST_JSID_TO_INT, RUST_JSID_TO_STRING}; use js::glue::{RUST_FUNCTION_VALUE_TO_JITINFO, RUST_JSID_IS_INT, RUST_JSID_IS_STRING}; use js::jsapi::HandleId as RawHandleId; use js::jsapi::HandleObject as RawHandleObject; use js::jsapi::MutableHandleObject as RawMutableHandleObject; use js::jsapi::{AutoIdVector, CallArgs, DOMCallbacks, GetNonCCWObjectGlobal}; use js::jsapi::{Heap, JSAutoRealm, JSContext, JS_FreezeObject}; use js::jsapi::{JSJitInfo, JSObject, JSTracer, JSWrapObjectCallbacks}; use js::jsapi::{JS_EnumerateStandardClasses, JS_GetLatin1StringCharsAndLength}; use js::jsapi::{JS_IsExceptionPending, JS_IsGlobalObject}; use js::jsapi::{JS_ResolveStandardClass, JS_StringHasLatin1Chars, ObjectOpResult}; use js::jsval::{JSVal, UndefinedValue}; use js::rust::wrappers::JS_DeletePropertyById; use js::rust::wrappers::JS_ForwardGetPropertyTo; use js::rust::wrappers::JS_GetProperty; use js::rust::wrappers::JS_GetPrototype; use js::rust::wrappers::JS_HasProperty; use js::rust::wrappers::JS_HasPropertyById; use js::rust::wrappers::JS_SetProperty; use js::rust::{get_object_class, is_dom_class, GCMethods, ToString, ToWindowProxyIfWindow}; use js::rust::{Handle, HandleId, HandleObject, HandleValue, MutableHandleValue}; use js::JS_CALLEE; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use std::ffi::CString; use std::os::raw::{c_char, c_void}; use std::ptr; use std::slice; /// Proxy handler for a WindowProxy. pub struct WindowProxyHandler(pub *const libc::c_void); impl MallocSizeOf for WindowProxyHandler { fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { // FIXME(#6907) this is a pointer to memory allocated by `new` in NewProxyHandler in rust-mozjs. 0 } } #[derive(JSTraceable, MallocSizeOf)] /// Static data associated with a global object. pub struct GlobalStaticData { /// The WindowProxy proxy handler for this global. pub windowproxy_handler: WindowProxyHandler, } impl GlobalStaticData { /// Creates a new GlobalStaticData. pub fn new() -> GlobalStaticData { GlobalStaticData { windowproxy_handler: windowproxy::new_window_proxy_handler(), } } } /// The index of the slot where the object holder of that interface's /// unforgeable members are defined. pub const DOM_PROTO_UNFORGEABLE_HOLDER_SLOT: u32 = 0; /// The index of the slot that contains a reference to the ProtoOrIfaceArray. // All DOM globals must have a slot at DOM_PROTOTYPE_SLOT. pub const DOM_PROTOTYPE_SLOT: u32 = js::JSCLASS_GLOBAL_SLOT_COUNT; /// The flag set on the `JSClass`es for DOM global objects. // NOTE: This is baked into the Ion JIT as 0 in codegen for LGetDOMProperty and // LSetDOMProperty. Those constants need to be changed accordingly if this value // changes. pub const JSCLASS_DOM_GLOBAL: u32 = js::JSCLASS_USERBIT1; /// The struct that holds inheritance information for DOM object reflectors. #[derive(Clone, Copy)] pub struct DOMClass { /// A list of interfaces that this object implements, in order of decreasing /// derivedness. pub interface_chain: [PrototypeList::ID; MAX_PROTO_CHAIN_LENGTH], /// The type ID of that interface. pub type_id: TopTypeId, /// The MallocSizeOf function wrapper for that interface. pub malloc_size_of: unsafe fn(ops: &mut MallocSizeOfOps, *const c_void) -> usize, /// The `Globals` flag for this global interface, if any. pub global: InterfaceObjectMap::Globals, } unsafe impl Sync for DOMClass {} /// The JSClass used for DOM object reflectors. #[derive(Copy)] pub struct DOMJSClass { /// The actual JSClass. pub base: js::jsapi::JSClass, /// Associated data for DOM object reflectors. pub dom_class: DOMClass, } impl Clone for DOMJSClass { fn clone(&self) -> DOMJSClass { *self } } unsafe impl Sync for DOMJSClass {} /// Returns a JSVal representing a frozen array of ports pub fn message_ports_to_frozen_array( message_ports: &[DomRoot], cx: SafeJSContext, ) -> JSVal { rooted!(in(*cx) let mut ports = UndefinedValue()); unsafe { message_ports.to_jsval(*cx, ports.handle_mut()) }; rooted!(in(*cx) let obj = ports.to_object()); unsafe { JS_FreezeObject(*cx, RawHandleObject::from(obj.handle())) }; *ports } /// Returns the ProtoOrIfaceArray for the given global object. /// Fails if `global` is not a DOM global object. pub fn get_proto_or_iface_array(global: *mut JSObject) -> *mut ProtoOrIfaceArray { unsafe { assert_ne!(((*get_object_class(global)).flags & JSCLASS_DOM_GLOBAL), 0); let mut slot = UndefinedValue(); JS_GetReservedSlot(global, DOM_PROTOTYPE_SLOT, &mut slot); slot.to_private() as *mut ProtoOrIfaceArray } } /// An array of *mut JSObject of size PROTO_OR_IFACE_LENGTH. pub type ProtoOrIfaceArray = [*mut JSObject; PROTO_OR_IFACE_LENGTH]; /// Gets the property `id` on `proxy`'s prototype. If it exists, `*found` is /// set to true and `*vp` to the value, otherwise `*found` is set to false. /// /// Returns false on JSAPI failure. pub unsafe fn get_property_on_prototype( cx: *mut JSContext, proxy: HandleObject, receiver: HandleValue, id: HandleId, found: *mut bool, vp: MutableHandleValue, ) -> bool { rooted!(in(cx) let mut proto = ptr::null_mut::()); if !JS_GetPrototype(cx, proxy, proto.handle_mut()) || proto.is_null() { *found = false; return true; } let mut has_property = false; if !JS_HasPropertyById(cx, proto.handle(), id, &mut has_property) { return false; } *found = has_property; if !has_property { return true; } JS_ForwardGetPropertyTo(cx, proto.handle(), id, receiver, vp) } /// Get an array index from the given `jsid`. Returns `None` if the given /// `jsid` is not an integer. pub fn get_array_index_from_id(_cx: *mut JSContext, id: HandleId) -> Option { let raw_id = id.into(); unsafe { if RUST_JSID_IS_INT(raw_id) { return Some(RUST_JSID_TO_INT(raw_id) as u32); } None } // if id is length atom, -1, otherwise // return if JSID_IS_ATOM(id) { // let atom = JSID_TO_ATOM(id); // //let s = *GetAtomChars(id); // if s > 'a' && s < 'z' { // return -1; // } // // let i = 0; // let str = AtomToLinearString(JSID_TO_ATOM(id)); // return if StringIsArray(str, &mut i) != 0 { i } else { -1 } // } else { // IdToInt32(cx, id); // } } /// Find the enum equivelent of a string given by `v` in `pairs`. /// Returns `Err(())` on JSAPI failure (there is a pending exception), and /// `Ok((None, value))` if there was no matching string. pub unsafe fn find_enum_value<'a, T>( cx: *mut JSContext, v: HandleValue, pairs: &'a [(&'static str, T)], ) -> Result<(Option<&'a T>, DOMString), ()> { let jsstr = ToString(cx, v); if jsstr.is_null() { return Err(()); } let search = jsstring_to_str(cx, jsstr); Ok(( pairs .iter() .find(|&&(key, _)| search == *key) .map(|&(_, ref ev)| ev), search, )) } /// Returns wether `obj` is a platform object /// pub fn is_platform_object(obj: *mut JSObject, cx: *mut JSContext) -> bool { unsafe { // Fast-path the common case let mut clasp = get_object_class(obj); if is_dom_class(&*clasp) { return true; } // Now for simplicity check for security wrappers before anything else if IsWrapper(obj) { let unwrapped_obj = UnwrapObjectDynamic(obj, cx, /* stopAtWindowProxy = */ 0); if unwrapped_obj.is_null() { return false; } clasp = get_object_class(obj); } // TODO also check if JS_IsArrayBufferObject is_dom_class(&*clasp) } } /// Get the property with name `property` from `object`. /// Returns `Err(())` on JSAPI failure (there is a pending exception), and /// `Ok(false)` if there was no property with the given name. pub fn get_dictionary_property( cx: *mut JSContext, object: HandleObject, property: &str, rval: MutableHandleValue, ) -> Result { fn has_property( cx: *mut JSContext, object: HandleObject, property: &CString, found: &mut bool, ) -> bool { unsafe { JS_HasProperty(cx, object, property.as_ptr(), found) } } fn get_property( cx: *mut JSContext, object: HandleObject, property: &CString, value: MutableHandleValue, ) -> bool { unsafe { JS_GetProperty(cx, object, property.as_ptr(), value) } } let property = CString::new(property).unwrap(); if object.get().is_null() { return Ok(false); } let mut found = false; if !has_property(cx, object, &property, &mut found) { return Err(()); } if !found { return Ok(false); } if !get_property(cx, object, &property, rval) { return Err(()); } Ok(true) } /// Set the property with name `property` from `object`. /// Returns `Err(())` on JSAPI failure, or null object, /// and Ok(()) otherwise pub fn set_dictionary_property( cx: *mut JSContext, object: HandleObject, property: &str, value: HandleValue, ) -> Result<(), ()> { if object.get().is_null() { return Err(()); } let property = CString::new(property).unwrap(); unsafe { if !JS_SetProperty(cx, object, property.as_ptr(), value) { return Err(()); } } Ok(()) } /// Returns whether `proxy` has a property `id` on its prototype. pub unsafe fn has_property_on_prototype( cx: *mut JSContext, proxy: HandleObject, id: HandleId, found: &mut bool, ) -> bool { rooted!(in(cx) let mut proto = ptr::null_mut::()); if !JS_GetPrototype(cx, proxy, proto.handle_mut()) { return false; } assert!(!proto.is_null()); JS_HasPropertyById(cx, proto.handle(), id, found) } /// Drop the resources held by reserved slots of a global object pub unsafe fn finalize_global(obj: *mut JSObject) { let protolist = get_proto_or_iface_array(obj); let list = (*protolist).as_mut_ptr(); for idx in 0..PROTO_OR_IFACE_LENGTH as isize { let entry = list.offset(idx); let value = *entry; <*mut JSObject>::post_barrier(entry, value, ptr::null_mut()); } let _: Box = Box::from_raw(protolist); } /// Trace the resources held by reserved slots of a global object pub unsafe fn trace_global(tracer: *mut JSTracer, obj: *mut JSObject) { let array = get_proto_or_iface_array(obj); for proto in (*array).iter() { if !proto.is_null() { trace_object( tracer, "prototype", &*(proto as *const *mut JSObject as *const Heap<*mut JSObject>), ); } } } /// Enumerate lazy properties of a global object. pub unsafe extern "C" fn enumerate_global( cx: *mut JSContext, obj: RawHandleObject, _props: *mut AutoIdVector, _enumerable_only: bool, ) -> bool { assert!(JS_IsGlobalObject(obj.get())); if !JS_EnumerateStandardClasses(cx, obj) { return false; } for init_fun in InterfaceObjectMap::MAP.values() { init_fun(SafeJSContext::from_ptr(cx), Handle::from_raw(obj)); } true } /// Resolve a lazy global property, for interface objects and named constructors. pub unsafe extern "C" fn resolve_global( cx: *mut JSContext, obj: RawHandleObject, id: RawHandleId, rval: *mut bool, ) -> bool { assert!(JS_IsGlobalObject(obj.get())); if !JS_ResolveStandardClass(cx, obj, id, rval) { return false; } if *rval { return true; } if !RUST_JSID_IS_STRING(id) { *rval = false; return true; } let string = RUST_JSID_TO_STRING(id); if !JS_StringHasLatin1Chars(string) { *rval = false; return true; } let mut length = 0; let ptr = JS_GetLatin1StringCharsAndLength(cx, ptr::null(), string, &mut length); assert!(!ptr.is_null()); let bytes = slice::from_raw_parts(ptr, length as usize); if let Some(init_fun) = InterfaceObjectMap::MAP.get(bytes) { init_fun(SafeJSContext::from_ptr(cx), Handle::from_raw(obj)); *rval = true; } else { *rval = false; } true } unsafe extern "C" fn wrap( cx: *mut JSContext, _existing: RawHandleObject, obj: RawHandleObject, ) -> *mut JSObject { // FIXME terrible idea. need security wrappers // https://github.com/servo/servo/issues/2382 WrapperNew(cx, obj, GetCrossCompartmentWrapper(), ptr::null(), false) } unsafe extern "C" fn pre_wrap( cx: *mut JSContext, _scope: RawHandleObject, obj: RawHandleObject, _object_passed_to_wrap: RawHandleObject, rval: RawMutableHandleObject, ) { let _ac = JSAutoRealm::new(cx, obj.get()); let obj = ToWindowProxyIfWindow(obj.get()); assert!(!obj.is_null()); rval.set(obj) } /// Callback table for use with JS_SetWrapObjectCallbacks pub static WRAP_CALLBACKS: JSWrapObjectCallbacks = JSWrapObjectCallbacks { wrap: Some(wrap), preWrap: Some(pre_wrap), }; /// Deletes the property `id` from `object`. pub unsafe fn delete_property_by_id( cx: *mut JSContext, object: HandleObject, id: HandleId, bp: *mut ObjectOpResult, ) -> bool { JS_DeletePropertyById(cx, object, id, bp) } unsafe fn generic_call( cx: *mut JSContext, argc: libc::c_uint, vp: *mut JSVal, is_lenient: bool, call: unsafe extern "C" fn( *const JSJitInfo, *mut JSContext, RawHandleObject, *mut libc::c_void, u32, *mut JSVal, ) -> bool, ) -> bool { let args = CallArgs::from_vp(vp, argc); let info = RUST_FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp)); let proto_id = (*info).protoID; let thisobj = args.thisv(); if !thisobj.get().is_null_or_undefined() && !thisobj.get().is_object() { throw_invalid_this(cx, proto_id); return false; } rooted!(in(cx) let obj = if thisobj.get().is_object() { thisobj.get().to_object() } else { GetNonCCWObjectGlobal(JS_CALLEE(cx, vp).to_object_or_null()) }); let depth = (*info).depth; let proto_check = |class: &'static DOMClass| class.interface_chain[depth as usize] as u16 == proto_id; let this = match private_from_proto_check(obj.get(), cx, proto_check) { Ok(val) => val, Err(()) => { if is_lenient { debug_assert!(!JS_IsExceptionPending(cx)); *vp = UndefinedValue(); return true; } else { throw_invalid_this(cx, proto_id); return false; } }, }; call( info, cx, obj.handle().into(), this as *mut libc::c_void, argc, vp, ) } /// Generic method of IDL interface. pub unsafe extern "C" fn generic_method( cx: *mut JSContext, argc: libc::c_uint, vp: *mut JSVal, ) -> bool { generic_call(cx, argc, vp, false, CallJitMethodOp) } /// Generic getter of IDL interface. pub unsafe extern "C" fn generic_getter( cx: *mut JSContext, argc: libc::c_uint, vp: *mut JSVal, ) -> bool { generic_call(cx, argc, vp, false, CallJitGetterOp) } /// Generic lenient getter of IDL interface. pub unsafe extern "C" fn generic_lenient_getter( cx: *mut JSContext, argc: libc::c_uint, vp: *mut JSVal, ) -> bool { generic_call(cx, argc, vp, true, CallJitGetterOp) } unsafe extern "C" fn call_setter( info: *const JSJitInfo, cx: *mut JSContext, handle: RawHandleObject, this: *mut libc::c_void, argc: u32, vp: *mut JSVal, ) -> bool { if !CallJitSetterOp(info, cx, handle, this, argc, vp) { return false; } *vp = UndefinedValue(); true } /// Generic setter of IDL interface. pub unsafe extern "C" fn generic_setter( cx: *mut JSContext, argc: libc::c_uint, vp: *mut JSVal, ) -> bool { generic_call(cx, argc, vp, false, call_setter) } /// Generic lenient setter of IDL interface. pub unsafe extern "C" fn generic_lenient_setter( cx: *mut JSContext, argc: libc::c_uint, vp: *mut JSVal, ) -> bool { generic_call(cx, argc, vp, true, call_setter) } unsafe extern "C" fn instance_class_has_proto_at_depth( clasp: *const js::jsapi::Class, proto_id: u32, depth: u32, ) -> bool { let domclass: *const DOMJSClass = clasp as *const _; let domclass = &*domclass; domclass.dom_class.interface_chain[depth as usize] as u32 == proto_id } #[allow(missing_docs)] // FIXME pub const DOM_CALLBACKS: DOMCallbacks = DOMCallbacks { instanceClassMatchesProto: Some(instance_class_has_proto_at_depth), }; // Generic method for returning libc::c_void from caller pub trait AsVoidPtr { fn as_void_ptr(&self) -> *const libc::c_void; } impl AsVoidPtr for T { fn as_void_ptr(&self) -> *const libc::c_void { self as *const T as *const libc::c_void } } // Generic method for returning c_char from caller pub trait AsCCharPtrPtr { fn as_c_char_ptr(&self) -> *const c_char; } impl AsCCharPtrPtr for [u8] { fn as_c_char_ptr(&self) -> *const c_char { self as *const [u8] as *const c_char } }