/* 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/. */ //! Various utilities to glue JavaScript and the DOM implementation together. use dom::bindings::codegen::PrototypeList; use dom::bindings::codegen::PrototypeList::{MAX_PROTO_CHAIN_LENGTH, PROTO_OR_IFACE_LENGTH}; use dom::bindings::conversions::{DOM_OBJECT_SLOT, is_dom_class}; use dom::bindings::conversions::{private_from_proto_check, root_from_handleobject}; use dom::bindings::error::throw_invalid_this; use dom::bindings::inheritance::TopTypeId; use dom::bindings::trace::trace_object; use dom::browsingcontext; use dom::window; use js; use js::error::throw_type_error; use js::glue::{CallJitGetterOp, CallJitMethodOp, CallJitSetterOp, IsWrapper}; use js::glue::{GetCrossCompartmentWrapper, WrapperNew}; use js::glue::{RUST_FUNCTION_VALUE_TO_JITINFO, RUST_JSID_IS_INT}; use js::glue::{RUST_JSID_TO_INT, UnwrapObject}; use js::jsapi::{CallArgs, CompartmentOptions, DOMCallbacks, GetGlobalForObjectCrossCompartment}; use js::jsapi::{HandleId, HandleObject, HandleValue, Heap, JSAutoCompartment, JSClass, JSContext}; use js::jsapi::{JSJitInfo, JSObject, JSTraceOp, JSTracer, JSVersion, JSWrapObjectCallbacks}; use js::jsapi::{JS_DeletePropertyById1, JS_FireOnNewGlobalObject}; use js::jsapi::{JS_ForwardGetPropertyTo, JS_GetClass, JS_GetProperty, JS_GetPrototype}; use js::jsapi::{JS_GetReservedSlot, JS_HasProperty, JS_HasPropertyById, JS_InitStandardClasses}; use js::jsapi::{JS_IsExceptionPending, JS_NewGlobalObject, JS_ObjectToOuterObject, JS_SetProperty}; use js::jsapi::{JS_SetReservedSlot, MutableHandleValue, ObjectOpResult, OnNewGlobalHookOption}; use js::jsapi::{RootedObject}; use js::jsval::{JSVal}; use js::jsval::{PrivateValue, UndefinedValue}; use js::rust::{GCMethods, ToString}; use js::{JS_CALLEE}; use libc::{self, c_uint}; use std::default::Default; use std::ffi::CString; use std::ptr; use util::mem::HeapSizeOf; use util::non_geckolib::jsstring_to_str; /// Proxy handler for a WindowProxy. pub struct WindowProxyHandler(pub *const libc::c_void); impl HeapSizeOf for WindowProxyHandler { fn heap_size_of_children(&self) -> usize { // FIXME(#6907) this is a pointer to memory allocated by `new` in NewProxyHandler in rust-mozjs. 0 } } #[derive(JSTraceable, HeapSizeOf)] /// 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: browsingcontext::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(Copy, Clone)] 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 HeapSizeOf function wrapper for that interface. pub heap_size_of: unsafe fn(*const libc::c_void) -> usize, } unsafe impl Sync for DOMClass {} /// The JSClass used for DOM object reflectors. #[derive(Copy)] pub struct DOMJSClass { /// The actual JSClass. pub base: js::jsapi::Class, /// 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 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!(((*JS_GetClass(global)).flags & JSCLASS_DOM_GLOBAL) != 0); JS_GetReservedSlot(global, DOM_PROTOTYPE_SLOT).to_private() as *mut ProtoOrIfaceArray } } /// A throwing constructor, for those interfaces that have neither /// `NoInterfaceObject` nor `Constructor`. pub unsafe extern "C" fn throwing_constructor(cx: *mut JSContext, _argc: c_uint, _vp: *mut JSVal) -> bool { throw_type_error(cx, "Illegal constructor."); false } /// 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 fn get_property_on_prototype(cx: *mut JSContext, proxy: HandleObject, id: HandleId, found: *mut bool, vp: MutableHandleValue) -> bool { unsafe { // let proto = GetObjectProto(proxy); let mut proto = RootedObject::new(cx, ptr::null_mut()); if !JS_GetPrototype(cx, proxy, proto.handle_mut()) || proto.ptr.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; let no_output = vp.ptr.is_null(); if !has_property || no_output { return true; } JS_ForwardGetPropertyTo(cx, proto.handle(), id, proxy, 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 { unsafe { if RUST_JSID_IS_INT(id) { return Some(RUST_JSID_TO_INT(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 index of a string given by `v` in `values`. /// Returns `Err(())` on JSAPI failure (there is a pending exception), and /// `Ok(None)` if there was no matching string. pub unsafe fn find_enum_string_index(cx: *mut JSContext, v: HandleValue, values: &[&'static str]) -> Result, ()> { let jsstr = ToString(cx, v); if jsstr.is_null() { return Err(()); } let search = jsstring_to_str(cx, jsstr); Ok(values.iter().position(|value| search == *value)) } /// Returns wether `obj` is a platform object /// https://heycam.github.io/webidl/#dfn-platform-object pub fn is_platform_object(obj: *mut JSObject) -> bool { unsafe { // Fast-path the common case let mut clasp = JS_GetClass(obj); if is_dom_class(&*clasp) { return true; } // Now for simplicity check for security wrappers before anything else if IsWrapper(obj) { let unwrapped_obj = UnwrapObject(obj, /* stopAtOuter = */ 0); if unwrapped_obj.is_null() { return false; } clasp = js::jsapi::JS_GetClass(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 fn has_property_on_prototype(cx: *mut JSContext, proxy: HandleObject, id: HandleId) -> bool { // MOZ_ASSERT(js::IsProxy(proxy) && js::GetProxyHandler(proxy) == handler); let mut found = false; !get_property_on_prototype(cx, proxy, id, &mut found, unsafe { MutableHandleValue::from_marked_location(ptr::null_mut()) }) || found } /// Create a DOM global object with the given class. pub fn create_dom_global(cx: *mut JSContext, class: *const JSClass, private: *const libc::c_void, trace: JSTraceOp) -> *mut JSObject { unsafe { let mut options = CompartmentOptions::default(); options.version_ = JSVersion::JSVERSION_ECMA_5; options.traceGlobal_ = trace; let obj = RootedObject::new(cx, JS_NewGlobalObject(cx, class, ptr::null_mut(), OnNewGlobalHookOption::DontFireOnNewGlobalHook, &options)); if obj.ptr.is_null() { return ptr::null_mut(); } // Initialize the reserved slots before doing amything that can GC, to // avoid getting trace hooks called on a partially initialized object. JS_SetReservedSlot(obj.ptr, DOM_OBJECT_SLOT, PrivateValue(private)); let proto_array: Box = box [0 as *mut JSObject; PROTO_OR_IFACE_LENGTH]; JS_SetReservedSlot(obj.ptr, DOM_PROTOTYPE_SLOT, PrivateValue(Box::into_raw(proto_array) as *const libc::c_void)); let _ac = JSAutoCompartment::new(cx, obj.ptr); JS_InitStandardClasses(cx, obj.handle()); JS_FireOnNewGlobalObject(cx, obj.handle()); obj.ptr } } /// 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; if <*mut JSObject>::needs_post_barrier(value) { <*mut JSObject>::relocate(entry); } } 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>)); } } } unsafe extern "C" fn wrap(cx: *mut JSContext, _existing: HandleObject, obj: HandleObject) -> *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, _existing: HandleObject, obj: HandleObject, _object_passed_to_wrap: HandleObject) -> *mut JSObject { let _ac = JSAutoCompartment::new(cx, obj.get()); JS_ObjectToOuterObject(cx, obj) } /// Callback table for use with JS_SetWrapObjectCallbacks pub static WRAP_CALLBACKS: JSWrapObjectCallbacks = JSWrapObjectCallbacks { wrap: Some(wrap), preWrap: Some(pre_wrap), }; /// Callback to outerize windows. pub unsafe extern "C" fn outerize_global(_cx: *mut JSContext, obj: HandleObject) -> *mut JSObject { debug!("outerizing"); let win = root_from_handleobject::(obj).unwrap(); let context = win.browsing_context(); context.as_ref().unwrap().window_proxy() } /// 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_DeletePropertyById1(cx, object, id, bp) } unsafe fn generic_call(cx: *mut JSContext, argc: libc::c_uint, vp: *mut JSVal, is_lenient: bool, call: unsafe extern fn(*const JSJitInfo, *mut JSContext, HandleObject, *mut libc::c_void, u32, *mut JSVal) -> bool) -> bool { let args = CallArgs::from_vp(vp, argc); let thisobj = args.thisv(); if !thisobj.get().is_null_or_undefined() && !thisobj.get().is_object() { return false; } let obj = if thisobj.get().is_object() { thisobj.get().to_object() } else { GetGlobalForObjectCrossCompartment(JS_CALLEE(cx, vp).to_object_or_null()) }; let obj = RootedObject::new(cx, obj); let info = RUST_FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp)); let proto_id = (*info).protoID; 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.ptr, 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(), 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: HandleObject, 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), };