/* 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/. */

//! Rust wrappers around the raw JS apis

use ar::AutoRealm;
use glue::{
    AppendToRootedObjectVector, CreateCallArgsFromVp, CreateRootedObjectVector,
    DeleteRootedObjectVector, IsDebugBuild,
};
use glue::{
    CreateRootedIdVector, DestroyRootedIdVector, GetMutableHandleIdVector, SliceRootedIdVector,
};
use glue::{DeleteCompileOptions, NewCompileOptions};
use jsapi::root::*;
use jsval::{self, UndefinedValue};
use libc::c_uint;
use panic;
use std::cell::{Cell, UnsafeCell};
use std::char;
use std::default::Default;
use std::ffi;
use std::marker;
use std::mem;
use std::ops::{Deref, DerefMut};
use std::ptr;
use std::slice;
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering};
use std::sync::mpsc::{sync_channel, SyncSender};
use std::sync::{Arc, Mutex, Once, ONCE_INIT};
use std::thread;
use std::u32;

const DEFAULT_HEAPSIZE: u32 = 32_u32 * 1024_u32 * 1024_u32;

// From Gecko:
// Our "default" stack is what we use in configurations where we don't have a compelling reason to
// do things differently. This is effectively 1MB on 64-bit platforms.
const STACK_QUOTA: usize = 128 * 8 * 1024;

// From Gecko:
// (See js/xpconnect/src/XPCJSContext.cpp)
// We tune the trusted/untrusted quotas for each configuration to achieve our
// invariants while attempting to minimize overhead. In contrast, our buffer
// between system code and trusted script is a very unscientific 10k.
const SYSTEM_CODE_BUFFER: usize = 10 * 1024;

// Gecko's value on 64-bit.
const TRUSTED_SCRIPT_BUFFER: usize = 8 * 12800;

trait ToResult {
    fn to_result(self) -> Result<(), ()>;
}

impl ToResult for bool {
    fn to_result(self) -> Result<(), ()> {
        if self {
            Ok(())
        } else {
            Err(())
        }
    }
}

// ___________________________________________________________________________
// friendly Rustic API to runtimes

thread_local!(static CONTEXT: Cell<*mut JSContext> = Cell::new(ptr::null_mut()));

lazy_static! {
    static ref OUTSTANDING_RUNTIMES: AtomicUsize = AtomicUsize::new(0);
    static ref SHUT_DOWN: AtomicBool = AtomicBool::new(false);
    static ref SHUT_DOWN_SIGNAL: Arc<Mutex<Option<SyncSender<()>>>> = Arc::new(Mutex::new(None));
}

/// A wrapper for the `JSContext` structure in SpiderMonkey.
pub struct Runtime {
    cx: *mut JSContext,
}

impl Runtime {
    /// Get the `JSContext` for this thread.
    pub fn get() -> *mut JSContext {
        let cx = CONTEXT.with(|context| context.get());
        assert!(!cx.is_null());
        cx
    }

    /// Creates a new `JSContext`.
    ///
    /// * `use_internal_job_queue`: If `true`, then SpiderMonkey's internal
    /// micro-task job queue is used. If `false`, then it is up to you to
    /// implement micro-tasks yourself.
    pub fn new(use_internal_job_queue: bool) -> Result<Runtime, ()> {
        if SHUT_DOWN.load(Ordering::SeqCst) {
            return Err(());
        }

        OUTSTANDING_RUNTIMES.fetch_add(1, Ordering::SeqCst);

        unsafe {
            #[derive(Debug)]
            struct Parent(UnsafeCell<*mut JSContext>);
            unsafe impl ::std::marker::Sync for Parent {}

            impl Parent {
                fn set(&self, val: *mut JSContext) {
                    self.as_atomic().store(val, Ordering::SeqCst);
                    assert_eq!(self.get(), val);
                }

                fn get(&self) -> *mut JSContext {
                    self.as_atomic().load(Ordering::SeqCst)
                }

                fn as_atomic(&self) -> &AtomicPtr<JSContext> {
                    unsafe { mem::transmute(&self.0) }
                }
            }

            lazy_static! {
                static ref PARENT: Parent = Parent(UnsafeCell::new(0 as *mut _));
            }
            static ONCE: Once = ONCE_INIT;

            ONCE.call_once(|| {
                // There is a 1:1 relationship between threads and JSContexts,
                // so we must spawn a new thread for the parent context.
                let (tx, rx) = sync_channel(0);
                *SHUT_DOWN_SIGNAL.lock().unwrap() = Some(tx);
                let _ = thread::spawn(move || {
                    let is_debug_mozjs = cfg!(feature = "debugmozjs");
                    let diagnostic = JS::detail::InitWithFailureDiagnostic(is_debug_mozjs);
                    if !diagnostic.is_null() {
                        panic!(
                            "JS::detail::InitWithFailureDiagnostic failed: {}",
                            ffi::CStr::from_ptr(diagnostic).to_string_lossy()
                        );
                    }

                    let context = JS_NewContext(DEFAULT_HEAPSIZE, ptr::null_mut());
                    assert!(!context.is_null());
                    JS::InitSelfHostedCode(context);
                    PARENT.set(context);

                    // The last JSRuntime child died, resume the execution by destroying the parent.
                    rx.recv().unwrap();
                    let cx = PARENT.get();
                    JS_DestroyContext(cx);
                    JS_ShutDown();
                    PARENT.set(0 as *mut _);
                    // Unblock the last child thread, waiting for the JS_ShutDown.
                    rx.recv().unwrap();
                });

                while PARENT.get().is_null() {
                    thread::yield_now();
                }
            });

            assert_eq!(IsDebugBuild(), cfg!(feature = "debugmozjs"));

            let js_context = JS_NewContext(DEFAULT_HEAPSIZE, JS_GetParentRuntime(PARENT.get()));
            assert!(!js_context.is_null());

            // Unconstrain the runtime's threshold on nominal heap size, to avoid
            // triggering GC too often if operating continuously near an arbitrary
            // finite threshold. This leaves the maximum-JS_malloc-bytes threshold
            // still in effect to cause periodical, and we hope hygienic,
            // last-ditch GCs from within the GC's allocator.
            JS_SetGCParameter(js_context, JSGCParamKey::JSGC_MAX_BYTES, u32::MAX);

            JS_SetNativeStackQuota(
                js_context,
                STACK_QUOTA,
                STACK_QUOTA - SYSTEM_CODE_BUFFER,
                STACK_QUOTA - SYSTEM_CODE_BUFFER - TRUSTED_SCRIPT_BUFFER,
            );

            CONTEXT.with(|context| {
                assert!(context.get().is_null());
                context.set(js_context);
            });

            if use_internal_job_queue {
                assert!(js::UseInternalJobQueues(js_context));
            }

            JS::InitSelfHostedCode(js_context);

            JS::SetWarningReporter(js_context, Some(report_warning));

            Ok(Runtime { cx: js_context })
        }
    }

    /// Returns the underlying `JSContext` object.
    pub fn cx(&self) -> *mut JSContext {
        self.cx
    }

    /// Returns the underlying `JSContext`'s `JSRuntime`.
    pub fn rt(&self) -> *mut JSRuntime {
        unsafe { JS_GetRuntime(self.cx) }
    }

    pub fn evaluate_script(
        &self,
        glob: JS::HandleObject,
        script: &str,
        filename: &str,
        line_num: u32,
        rval: JS::MutableHandleValue,
    ) -> Result<(), ()> {
        let script_utf16: Vec<u16> = script.encode_utf16().collect();
        let filename_cstr = ffi::CString::new(filename.as_bytes()).unwrap();
        debug!(
            "Evaluating script from {} with content {}",
            filename, script
        );
        // SpiderMonkey does not approve of null pointers.
        let (ptr, len) = if script_utf16.len() == 0 {
            static empty: &'static [u16] = &[];
            (empty.as_ptr(), 0)
        } else {
            (script_utf16.as_ptr(), script_utf16.len() as c_uint)
        };
        assert!(!ptr.is_null());
        unsafe {
            let _ar = AutoRealm::with_obj(self.cx(), glob.get());
            let options = CompileOptionsWrapper::new(self.cx(), filename_cstr.as_ptr(), line_num);

            let mut srcBuf = JS::SourceText {
                units_: ptr,
                length_: len as _,
                ownsUnits_: false,
                _phantom_0: marker::PhantomData,
            };
            if !JS::Evaluate(self.cx(), options.ptr, &mut srcBuf, rval) {
                debug!("...err!");
                panic::maybe_resume_unwind();
                Err(())
            } else {
                // we could return the script result but then we'd have
                // to root it and so forth and, really, who cares?
                debug!("...ok!");
                Ok(())
            }
        }
    }
}

impl Drop for Runtime {
    fn drop(&mut self) {
        unsafe {
            JS_DestroyContext(self.cx);

            CONTEXT.with(|context| {
                assert_eq!(context.get(), self.cx);
                context.set(ptr::null_mut());
            });

            if OUTSTANDING_RUNTIMES.fetch_sub(1, Ordering::SeqCst) == 1 {
                SHUT_DOWN.store(true, Ordering::SeqCst);
                let signal = &SHUT_DOWN_SIGNAL.lock().unwrap();
                let signal = signal.as_ref().unwrap();
                // Send a signal to shutdown the Parent runtime.
                signal.send(()).unwrap();
                // Wait for it to be destroyed before resuming the execution
                // of static variable destructors.
                signal.send(()).unwrap();
            }
        }
    }
}

// ___________________________________________________________________________
// Rooting API for standard JS things

pub trait RootKind {
    #[inline(always)]
    fn rootKind() -> JS::RootKind;
}

impl RootKind for *mut JSObject {
    #[inline(always)]
    fn rootKind() -> JS::RootKind {
        JS::RootKind::Object
    }
}

impl RootKind for *mut JSLinearString {
    #[inline(always)]
    fn rootKind() -> JS::RootKind {
        JS::RootKind::String
    }
}

impl RootKind for *mut JSFunction {
    #[inline(always)]
    fn rootKind() -> JS::RootKind {
        JS::RootKind::Object
    }
}

impl RootKind for *mut JSString {
    #[inline(always)]
    fn rootKind() -> JS::RootKind {
        JS::RootKind::String
    }
}

impl RootKind for *mut JS::Symbol {
    #[inline(always)]
    fn rootKind() -> JS::RootKind {
        JS::RootKind::Symbol
    }
}

#[cfg(feature = "bigint")]
impl RootKind for *mut JS::BigInt {
    #[inline(always)]
    fn rootKind() -> JS::RootKind {
        JS::RootKind::BigInt
    }
}

impl RootKind for *mut JSScript {
    #[inline(always)]
    fn rootKind() -> JS::RootKind {
        JS::RootKind::Script
    }
}

impl RootKind for jsid {
    #[inline(always)]
    fn rootKind() -> JS::RootKind {
        JS::RootKind::Id
    }
}

impl RootKind for JS::Value {
    #[inline(always)]
    fn rootKind() -> JS::RootKind {
        JS::RootKind::Value
    }
}

impl<T> JS::Rooted<T> {
    pub fn new_unrooted() -> JS::Rooted<T>
    where
        T: GCMethods,
    {
        JS::Rooted {
            stack: ptr::null_mut(),
            prev: ptr::null_mut(),
            ptr: unsafe { T::initial() },
            _phantom_0: marker::PhantomData,
        }
    }

    unsafe fn get_rooting_context(cx: *mut JSContext) -> *mut JS::RootingContext {
        mem::transmute(cx)
    }

    unsafe fn get_root_stack(
        cx: *mut JSContext,
    ) -> *mut *mut JS::Rooted<*mut ::std::os::raw::c_void>
    where
        T: RootKind,
    {
        let kind = T::rootKind() as usize;
        let rooting_cx = Self::get_rooting_context(cx);
        &mut rooting_cx.as_mut().unwrap().stackRoots_[kind] as *mut _ as *mut _
    }

    pub unsafe fn register_with_root_lists(&mut self, cx: *mut JSContext)
    where
        T: RootKind,
    {
        self.stack = Self::get_root_stack(cx);
        let stack = self.stack.as_mut().unwrap();
        self.prev = *stack as *mut _;

        *stack = self as *mut _ as usize as _;
    }

    pub unsafe fn remove_from_root_stack(&mut self) {
        assert!(*self.stack == self as *mut _ as usize as _);
        *self.stack = self.prev;
    }
}

/// Rust API for keeping a JS::Rooted value in the context's root stack.
/// Example usage: `rooted!(in(cx) let x = UndefinedValue());`.
/// `RootedGuard::new` also works, but the macro is preferred.
pub struct RootedGuard<'a, T: 'a + RootKind + GCMethods> {
    root: &'a mut JS::Rooted<T>,
}

impl<'a, T: 'a + RootKind + GCMethods> RootedGuard<'a, T> {
    pub fn new(cx: *mut JSContext, root: &'a mut JS::Rooted<T>, initial: T) -> Self {
        root.ptr = initial;
        unsafe {
            root.register_with_root_lists(cx);
        }
        RootedGuard { root: root }
    }

    pub fn handle(&self) -> JS::Handle<T> {
        unsafe { JS::Handle::from_marked_location(&self.root.ptr) }
    }

    pub fn handle_mut(&mut self) -> JS::MutableHandle<T> {
        unsafe { JS::MutableHandle::from_marked_location(&mut self.root.ptr) }
    }

    pub fn get(&self) -> T
    where
        T: Copy,
    {
        self.root.ptr
    }

    pub fn set(&mut self, v: T) {
        self.root.ptr = v;
    }
}

impl<'a, T: 'a + RootKind + GCMethods> Deref for RootedGuard<'a, T> {
    type Target = T;
    fn deref(&self) -> &T {
        &self.root.ptr
    }
}

impl<'a, T: 'a + RootKind + GCMethods> DerefMut for RootedGuard<'a, T> {
    fn deref_mut(&mut self) -> &mut T {
        &mut self.root.ptr
    }
}

impl<'a, T: 'a + RootKind + GCMethods> Drop for RootedGuard<'a, T> {
    fn drop(&mut self) {
        unsafe {
            self.root.ptr = T::initial();
            self.root.remove_from_root_stack();
        }
    }
}

#[macro_export]
macro_rules! rooted {
    (in($cx:expr) let $name:ident = $init:expr) => {
        let mut __root = $crate::jsapi::JS::Rooted::new_unrooted();
        let $name = $crate::rust::RootedGuard::new($cx, &mut __root, $init);
    };
    (in($cx:expr) let mut $name:ident = $init:expr) => {
        let mut __root = $crate::jsapi::JS::Rooted::new_unrooted();
        let mut $name = $crate::rust::RootedGuard::new($cx, &mut __root, $init);
    };
}

impl<T> JS::Handle<T> {
    pub fn get(&self) -> T
    where
        T: Copy,
    {
        unsafe { *self.ptr }
    }

    pub unsafe fn from_marked_location(ptr: *const T) -> JS::Handle<T> {
        JS::Handle {
            ptr: mem::transmute(ptr),
            _phantom_0: marker::PhantomData,
        }
    }
}

impl<T> Deref for JS::Handle<T> {
    type Target = T;

    fn deref<'a>(&'a self) -> &'a T {
        unsafe { &*self.ptr }
    }
}

impl<T> JS::MutableHandle<T> {
    pub unsafe fn from_marked_location(ptr: *mut T) -> JS::MutableHandle<T> {
        JS::MutableHandle {
            ptr: ptr,
            _phantom_0: marker::PhantomData,
        }
    }

    pub fn handle(&self) -> JS::Handle<T> {
        unsafe { JS::Handle::from_marked_location(self.ptr as *const _) }
    }

    pub fn get(&self) -> T
    where
        T: Copy,
    {
        unsafe { *self.ptr }
    }

    pub fn set(&self, v: T)
    where
        T: Copy,
    {
        unsafe { *self.ptr = v }
    }
}

impl<T> Deref for JS::MutableHandle<T> {
    type Target = T;

    fn deref<'a>(&'a self) -> &'a T {
        unsafe { &*self.ptr }
    }
}

impl<T> DerefMut for JS::MutableHandle<T> {
    fn deref_mut<'a>(&'a mut self) -> &'a mut T {
        unsafe { &mut *self.ptr }
    }
}

impl JS::HandleValue {
    pub fn null() -> JS::HandleValue {
        unsafe { JS::NullHandleValue }
    }

    pub fn undefined() -> JS::HandleValue {
        unsafe { JS::UndefinedHandleValue }
    }
}

impl JS::HandleValueArray {
    pub fn new() -> JS::HandleValueArray {
        JS::HandleValueArray {
            length_: 0,
            elements_: ptr::null(),
        }
    }

    pub unsafe fn from_rooted_slice(values: &[JS::Value]) -> JS::HandleValueArray {
        JS::HandleValueArray {
            length_: values.len(),
            elements_: values.as_ptr(),
        }
    }
}

const ConstNullValue: *mut JSObject = 0 as *mut JSObject;

impl JS::HandleObject {
    pub fn null() -> JS::HandleObject {
        unsafe { JS::HandleObject::from_marked_location(&ConstNullValue) }
    }
}

impl Default for jsid {
    fn default() -> jsid {
        jsid {
            asBits: JSID_TYPE_VOID as usize,
        }
    }
}

impl Default for JS::Value {
    fn default() -> JS::Value {
        jsval::UndefinedValue()
    }
}

impl Default for JS::RealmOptions {
    fn default() -> Self {
        unsafe { ::std::mem::zeroed() }
    }
}

pub trait GCMethods {
    unsafe fn initial() -> Self;
    unsafe fn write_barriers(v: *mut Self, prev: Self, next: Self);
}

impl GCMethods for jsid {
    unsafe fn initial() -> jsid {
        Default::default()
    }
    unsafe fn write_barriers(_: *mut jsid, _: jsid, _: jsid) {}
}

#[cfg(feature = "bigint")]
impl GCMethods for *mut JS::BigInt {
    unsafe fn initial() -> *mut JS::BigInt {
        ptr::null_mut()
    }
    unsafe fn write_barriers(
        v: *mut *mut JS::BigInt,
        prev: *mut JS::BigInt,
        next: *mut JS::BigInt,
    ) {
        JS::HeapBigIntWriteBarriers(v, prev, next);
    }
}

impl GCMethods for *mut JSObject {
    unsafe fn initial() -> *mut JSObject {
        ptr::null_mut()
    }
    unsafe fn write_barriers(v: *mut *mut JSObject, prev: *mut JSObject, next: *mut JSObject) {
        JS::HeapObjectWriteBarriers(v, prev, next);
    }
}

impl GCMethods for *mut JSString {
    unsafe fn initial() -> *mut JSString {
        ptr::null_mut()
    }
    unsafe fn write_barriers(v: *mut *mut JSString, prev: *mut JSString, next: *mut JSString) {
        JS::HeapStringWriteBarriers(v, prev, next);
    }
}

impl GCMethods for *mut JSScript {
    unsafe fn initial() -> *mut JSScript {
        ptr::null_mut()
    }
    unsafe fn write_barriers(v: *mut *mut JSScript, prev: *mut JSScript, next: *mut JSScript) {
        JS::HeapScriptWriteBarriers(v, prev, next);
    }
}

impl GCMethods for *mut JSFunction {
    unsafe fn initial() -> *mut JSFunction {
        ptr::null_mut()
    }
    unsafe fn write_barriers(
        v: *mut *mut JSFunction,
        prev: *mut JSFunction,
        next: *mut JSFunction,
    ) {
        JS::HeapObjectWriteBarriers(
            mem::transmute(v),
            mem::transmute(prev),
            mem::transmute(next),
        );
    }
}

impl GCMethods for JS::Value {
    unsafe fn initial() -> JS::Value {
        UndefinedValue()
    }
    unsafe fn write_barriers(v: *mut JS::Value, prev: JS::Value, next: JS::Value) {
        JS::HeapValueWriteBarriers(v, &prev, &next);
    }
}

// ___________________________________________________________________________
// Implementations for various things in jsapi.rs

impl Drop for JSAutoRealm {
    fn drop(&mut self) {
        unsafe {
            JS::LeaveRealm(self.cx_, self.oldRealm_);
        }
    }
}

impl JSJitMethodCallArgs {
    #[inline]
    pub fn get(&self, i: u32) -> JS::HandleValue {
        unsafe {
            if i < self._base.argc_ {
                JS::HandleValue::from_marked_location(self._base.argv_.offset(i as isize))
            } else {
                JS::UndefinedHandleValue
            }
        }
    }

    #[inline]
    pub fn index(&self, i: u32) -> JS::HandleValue {
        assert!(i < self._base.argc_);
        unsafe { JS::HandleValue::from_marked_location(self._base.argv_.offset(i as isize)) }
    }

    #[inline]
    pub fn index_mut(&self, i: u32) -> JS::MutableHandleValue {
        assert!(i < self._base.argc_);
        unsafe { JS::MutableHandleValue::from_marked_location(self._base.argv_.offset(i as isize)) }
    }

    #[inline]
    pub fn rval(&self) -> JS::MutableHandleValue {
        unsafe { JS::MutableHandleValue::from_marked_location(self._base.argv_.offset(-2)) }
    }
}

// XXX need to hack up bindgen to convert this better so we don't have
//     to duplicate so much code here
impl JS::CallArgs {
    #[inline]
    pub unsafe fn from_vp(vp: *mut JS::Value, argc: u32) -> JS::CallArgs {
        CreateCallArgsFromVp(argc, vp)
    }

    #[inline]
    pub fn index(&self, i: u32) -> JS::HandleValue {
        assert!(i < self._base.argc_);
        unsafe { JS::HandleValue::from_marked_location(self._base.argv_.offset(i as isize)) }
    }

    #[inline]
    pub fn index_mut(&self, i: u32) -> JS::MutableHandleValue {
        assert!(i < self._base.argc_);
        unsafe { JS::MutableHandleValue::from_marked_location(self._base.argv_.offset(i as isize)) }
    }

    #[inline]
    pub fn get(&self, i: u32) -> JS::HandleValue {
        unsafe {
            if i < self._base.argc_ {
                JS::HandleValue::from_marked_location(self._base.argv_.offset(i as isize))
            } else {
                JS::UndefinedHandleValue
            }
        }
    }

    #[inline]
    pub fn rval(&self) -> JS::MutableHandleValue {
        unsafe { JS::MutableHandleValue::from_marked_location(self._base.argv_.offset(-2)) }
    }

    #[inline]
    pub fn thisv(&self) -> JS::HandleValue {
        unsafe { JS::HandleValue::from_marked_location(self._base.argv_.offset(-1)) }
    }

    #[inline]
    pub fn calleev(&self) -> JS::HandleValue {
        unsafe { JS::HandleValue::from_marked_location(self._base.argv_.offset(-2)) }
    }

    #[inline]
    pub fn callee(&self) -> *mut JSObject {
        self.calleev().to_object()
    }

    #[inline]
    pub fn new_target(&self) -> JS::MutableHandleValue {
        assert!(self._base.constructing_());
        unsafe {
            JS::MutableHandleValue::from_marked_location(
                self._base.argv_.offset(self._base.argc_ as isize),
            )
        }
    }
}

impl JSJitGetterCallArgs {
    #[inline]
    pub fn rval(&self) -> JS::MutableHandleValue {
        self._base
    }
}

impl JSJitSetterCallArgs {
    #[inline]
    pub fn get(&self, i: u32) -> JS::HandleValue {
        assert!(i == 0);
        self._base.handle()
    }
}

// ___________________________________________________________________________
// Wrappers around things in jsglue.cpp

pub struct RootedObjectVectorWrapper {
    pub ptr: *mut JS::PersistentRootedObjectVector,
}

impl RootedObjectVectorWrapper {
    pub fn new(cx: *mut JSContext) -> RootedObjectVectorWrapper {
        RootedObjectVectorWrapper {
            ptr: unsafe { CreateRootedObjectVector(cx) },
        }
    }

    pub fn append(&self, obj: *mut JSObject) -> bool {
        unsafe { AppendToRootedObjectVector(self.ptr, obj) }
    }
}

impl Drop for RootedObjectVectorWrapper {
    fn drop(&mut self) {
        unsafe { DeleteRootedObjectVector(self.ptr) }
    }
}

pub struct CompileOptionsWrapper {
    pub ptr: *mut JS::ReadOnlyCompileOptions,
}

impl CompileOptionsWrapper {
    pub fn new(
        cx: *mut JSContext,
        file: *const ::libc::c_char,
        line: c_uint,
    ) -> CompileOptionsWrapper {
        CompileOptionsWrapper {
            ptr: unsafe { NewCompileOptions(cx, file, line) },
        }
    }
}

impl Drop for CompileOptionsWrapper {
    fn drop(&mut self) {
        unsafe { DeleteCompileOptions(self.ptr) }
    }
}

// ___________________________________________________________________________
// Fast inline converters

#[inline]
pub unsafe fn ToBoolean(v: JS::HandleValue) -> bool {
    let val = *v.ptr;

    if val.is_boolean() {
        return val.to_boolean();
    }

    if val.is_int32() {
        return val.to_int32() != 0;
    }

    if val.is_null_or_undefined() {
        return false;
    }

    if val.is_double() {
        let d = val.to_double();
        return !d.is_nan() && d != 0f64;
    }

    if val.is_symbol() {
        return true;
    }

    js::ToBooleanSlow(v)
}

#[inline]
pub unsafe fn ToNumber(cx: *mut JSContext, v: JS::HandleValue) -> Result<f64, ()> {
    let val = *v.ptr;
    if val.is_number() {
        return Ok(val.to_number());
    }

    let mut out = Default::default();
    if js::ToNumberSlow(cx, v, &mut out) {
        Ok(out)
    } else {
        Err(())
    }
}

#[inline]
unsafe fn convert_from_int32<T: Default + Copy>(
    cx: *mut JSContext,
    v: JS::HandleValue,
    conv_fn: unsafe extern "C" fn(*mut JSContext, JS::HandleValue, *mut T) -> bool,
) -> Result<T, ()> {
    let val = *v.ptr;
    if val.is_int32() {
        let intval: i64 = val.to_int32() as i64;
        // TODO: do something better here that works on big endian
        let intval = *(&intval as *const i64 as *const T);
        return Ok(intval);
    }

    let mut out = Default::default();
    if conv_fn(cx, v, &mut out) {
        Ok(out)
    } else {
        Err(())
    }
}

#[inline]
pub unsafe fn ToInt32(cx: *mut JSContext, v: JS::HandleValue) -> Result<i32, ()> {
    convert_from_int32::<i32>(cx, v, js::ToInt32Slow)
}

#[inline]
pub unsafe fn ToUint32(cx: *mut JSContext, v: JS::HandleValue) -> Result<u32, ()> {
    convert_from_int32::<u32>(cx, v, js::ToUint32Slow)
}

#[inline]
pub unsafe fn ToUint16(cx: *mut JSContext, v: JS::HandleValue) -> Result<u16, ()> {
    convert_from_int32::<u16>(cx, v, js::ToUint16Slow)
}

#[inline]
pub unsafe fn ToInt64(cx: *mut JSContext, v: JS::HandleValue) -> Result<i64, ()> {
    convert_from_int32::<i64>(cx, v, js::ToInt64Slow)
}

#[inline]
pub unsafe fn ToUint64(cx: *mut JSContext, v: JS::HandleValue) -> Result<u64, ()> {
    convert_from_int32::<u64>(cx, v, js::ToUint64Slow)
}

#[inline]
pub unsafe fn ToString(cx: *mut JSContext, v: JS::HandleValue) -> *mut JSString {
    let val = *v.ptr;
    if val.is_string() {
        return val.to_string();
    }

    js::ToStringSlow(cx, v)
}

pub unsafe extern "C" fn report_warning(_cx: *mut JSContext, report: *mut JSErrorReport) {
    fn latin1_to_string(bytes: &[u8]) -> String {
        bytes
            .iter()
            .map(|c| char::from_u32(*c as u32).unwrap())
            .collect()
    }

    let fnptr = (*report)._base.filename;
    let fname = if !fnptr.is_null() {
        let c_str = ffi::CStr::from_ptr(fnptr);
        latin1_to_string(c_str.to_bytes())
    } else {
        "none".to_string()
    };

    let lineno = (*report)._base.lineno;
    let column = (*report)._base.column;

    let msg_ptr = (*report)._base.message_.data_ as *const u16;
    let msg_len = (0usize..)
        .find(|&i| *msg_ptr.offset(i as isize) == 0)
        .unwrap();
    let msg_slice = slice::from_raw_parts(msg_ptr, msg_len);
    let msg = String::from_utf16_lossy(msg_slice);

    warn!("Warning at {}:{}:{}: {}\n", fname, lineno, column, msg);
}

impl JSNativeWrapper {
    fn is_zeroed(&self) -> bool {
        let JSNativeWrapper { op, info } = *self;
        op.is_none() && info.is_null()
    }
}

pub struct RootedIdVectorWrapper {
    pub ptr: *mut JS::PersistentRootedIdVector,
}

impl RootedIdVectorWrapper {
    pub fn new(cx: *mut JSContext) -> RootedIdVectorWrapper {
        RootedIdVectorWrapper {
            ptr: unsafe { CreateRootedIdVector(cx) },
        }
    }

    pub fn handle_mut(&self) -> JS::MutableHandleIdVector {
        unsafe { GetMutableHandleIdVector(self.ptr) }
    }
}

impl Drop for RootedIdVectorWrapper {
    fn drop(&mut self) {
        unsafe { DestroyRootedIdVector(self.ptr) }
    }
}

impl Deref for RootedIdVectorWrapper {
    type Target = [jsid];

    fn deref(&self) -> &[jsid] {
        unsafe {
            let mut length = 0;
            let pointer = SliceRootedIdVector(self.ptr as *const _, &mut length);
            slice::from_raw_parts(pointer, length)
        }
    }
}

/// Defines methods on `obj`. The last entry of `methods` must contain zeroed
/// memory.
///
/// # Failures
///
/// Returns `Err` on JSAPI failure.
///
/// # Panics
///
/// Panics if the last entry of `methods` does not contain zeroed memory.
///
/// # Safety
///
/// - `cx` must be valid.
/// - This function calls into unaudited C++ code.
pub unsafe fn define_methods(
    cx: *mut JSContext,
    obj: JS::HandleObject,
    methods: &'static [JSFunctionSpec],
) -> Result<(), ()> {
    assert!({
        match methods.last() {
            Some(&JSFunctionSpec {
                name: JSFunctionSpec_Name { string_: name },
                call,
                nargs,
                flags,
                selfHostedName,
            }) => {
                name.is_null()
                    && call.is_zeroed()
                    && nargs == 0
                    && flags == 0
                    && selfHostedName.is_null()
            }
            None => false,
        }
    });

    JS_DefineFunctions(cx, obj, methods.as_ptr()).to_result()
}

/// Defines attributes on `obj`. The last entry of `properties` must contain
/// zeroed memory.
///
/// # Failures
///
/// Returns `Err` on JSAPI failure.
///
/// # Panics
///
/// Panics if the last entry of `properties` does not contain zeroed memory.
///
/// # Safety
///
/// - `cx` must be valid.
/// - This function calls into unaudited C++ code.
pub unsafe fn define_properties(
    cx: *mut JSContext,
    obj: JS::HandleObject,
    properties: &'static [JSPropertySpec],
) -> Result<(), ()> {
    assert!(!properties.is_empty());
    assert!({
        let spec = properties.last().unwrap();
        let slice = slice::from_raw_parts(
            spec as *const _ as *const u8,
            mem::size_of::<JSPropertySpec>(),
        );
        slice.iter().all(|byte| *byte == 0)
    });

    JS_DefineProperties(cx, obj, properties.as_ptr()).to_result()
}

static SIMPLE_GLOBAL_CLASS_OPS: JSClassOps = JSClassOps {
    addProperty: None,
    delProperty: None,
    enumerate: Some(JS_EnumerateStandardClasses),
    newEnumerate: None,
    resolve: Some(JS_ResolveStandardClass),
    mayResolve: Some(JS_MayResolveStandardClass),
    finalize: None,
    call: None,
    hasInstance: None,
    construct: None,
    trace: Some(JS_GlobalObjectTraceHook),
};

/// This is a simple `JSClass` for global objects, primarily intended for tests.
pub static SIMPLE_GLOBAL_CLASS: JSClass = JSClass {
    name: b"Global\0" as *const u8 as *const _,
    flags: (JSCLASS_IS_GLOBAL
        | ((JSCLASS_GLOBAL_SLOT_COUNT & JSCLASS_RESERVED_SLOTS_MASK)
            << JSCLASS_RESERVED_SLOTS_SHIFT)) as u32,
    cOps: &SIMPLE_GLOBAL_CLASS_OPS as *const JSClassOps,
    spec: 0 as *mut _,
    ext: 0 as *mut _,
    oOps: 0 as *mut _,
};

#[inline]
unsafe fn get_object_group(obj: *mut JSObject) -> *mut js::shadow::ObjectGroup {
    assert!(!obj.is_null());
    let obj = obj as *mut js::shadow::Object;
    (*obj).group
}

#[inline]
pub unsafe fn get_object_class(obj: *mut JSObject) -> *const JSClass {
    (*get_object_group(obj)).clasp as *const _
}

#[inline]
pub unsafe fn get_object_compartment(obj: *mut JSObject) -> *mut JS::Compartment {
    let realm = (*get_object_group(obj)).realm as *const JS::shadow::Realm;
    (*realm).compartment_
}

#[inline]
pub fn is_dom_class(class: &JSClass) -> bool {
    class.flags & JSCLASS_IS_DOMJSCLASS != 0
}

#[inline]
pub unsafe fn is_dom_object(obj: *mut JSObject) -> bool {
    is_dom_class(&*get_object_class(obj))
}

#[inline]
pub unsafe fn is_global(obj: *mut JSObject) -> bool {
    (*get_object_class(obj)).flags & JSCLASS_IS_GLOBAL != 0
}

#[inline]
pub unsafe fn is_window(obj: *mut JSObject) -> bool {
    is_global(obj) && js::detail::IsWindowSlow(obj)
}

#[inline]
pub unsafe fn try_to_outerize(rval: JS::MutableHandleValue) {
    let obj = rval.to_object();
    if is_window(obj) {
        let obj = js::detail::ToWindowProxyIfWindowSlow(obj);
        assert!(!obj.is_null());
        rval.set(jsval::ObjectValue(&mut *obj));
    }
}

#[inline]
pub unsafe fn maybe_wrap_object_value(cx: *mut JSContext, rval: JS::MutableHandleValue) {
    assert!(rval.is_object());

    // There used to be inline checks if this out of line call was necessary or
    // not here, but JSAPI no longer exposes a way to get a JSContext's
    // compartment, and additionally JSContext is under a bunch of churn in
    // JSAPI in general right now.

    assert!(JS_WrapValue(cx, rval));
}

#[inline]
pub unsafe fn maybe_wrap_object_or_null_value(cx: *mut JSContext, rval: JS::MutableHandleValue) {
    assert!(rval.is_object_or_null());
    if !rval.is_null() {
        maybe_wrap_object_value(cx, rval);
    }
}

#[inline]
pub unsafe fn maybe_wrap_value(cx: *mut JSContext, rval: JS::MutableHandleValue) {
    if rval.is_string() {
        assert!(JS_WrapValue(cx, rval));
    } else if rval.is_object() {
        maybe_wrap_object_value(cx, rval);
    }
}

/// Equivalents of the JS_FN* macros.
impl JSFunctionSpec {
    pub fn js_fs(
        name: *const ::std::os::raw::c_char,
        func: JSNative,
        nargs: u16,
        flags: u16,
    ) -> JSFunctionSpec {
        JSFunctionSpec {
            name: JSFunctionSpec_Name { string_: name },
            call: JSNativeWrapper {
                op: func,
                info: ptr::null(),
            },
            nargs: nargs,
            flags: flags,
            selfHostedName: 0 as *const _,
        }
    }

    pub fn js_fn(
        name: *const ::std::os::raw::c_char,
        func: JSNative,
        nargs: u16,
        flags: u16,
    ) -> JSFunctionSpec {
        JSFunctionSpec {
            name: JSFunctionSpec_Name { string_: name },
            call: JSNativeWrapper {
                op: func,
                info: ptr::null(),
            },
            nargs: nargs,
            flags: flags,
            selfHostedName: 0 as *const _,
        }
    }

    pub const NULL: JSFunctionSpec = JSFunctionSpec {
        name: JSFunctionSpec_Name {
            string_: 0 as *const _,
        },
        call: JSNativeWrapper {
            op: None,
            info: 0 as *const _,
        },
        nargs: 0,
        flags: 0,
        selfHostedName: 0 as *const _,
    };
}

/// Equivalents of the JS_PS* macros.
impl JSPropertySpec {
    pub fn getter(
        name: *const ::std::os::raw::c_char,
        flags: u8,
        func: JSNative,
    ) -> JSPropertySpec {
        debug_assert_eq!(flags & !(JSPROP_ENUMERATE | JSPROP_PERMANENT), 0);
        JSPropertySpec {
            name: JSPropertySpec_Name { string_: name },
            flags_: flags,
            u: JSPropertySpec_AccessorsOrValue {
                accessors: JSPropertySpec_AccessorsOrValue_Accessors {
                    getter: JSPropertySpec_Accessor {
                        native: JSNativeWrapper {
                            op: func,
                            info: ptr::null(),
                        },
                    },
                    setter: JSPropertySpec_Accessor {
                        native: JSNativeWrapper {
                            op: None,
                            info: ptr::null(),
                        },
                    },
                },
            },
        }
    }

    pub fn getter_setter(
        name: *const ::std::os::raw::c_char,
        flags: u8,
        g_f: JSNative,
        s_f: JSNative,
    ) -> JSPropertySpec {
        debug_assert_eq!(flags & !(JSPROP_ENUMERATE | JSPROP_PERMANENT), 0);
        JSPropertySpec {
            name: JSPropertySpec_Name { string_: name },
            flags_: flags,
            u: JSPropertySpec_AccessorsOrValue {
                accessors: JSPropertySpec_AccessorsOrValue_Accessors {
                    getter: JSPropertySpec_Accessor {
                        native: JSNativeWrapper {
                            op: g_f,
                            info: ptr::null(),
                        },
                    },
                    setter: JSPropertySpec_Accessor {
                        native: JSNativeWrapper {
                            op: s_f,
                            info: ptr::null(),
                        },
                    },
                },
            },
        }
    }

    pub const NULL: JSPropertySpec = JSPropertySpec {
        name: JSPropertySpec_Name {
            string_: 0 as *const _,
        },
        flags_: 0,
        u: JSPropertySpec_AccessorsOrValue {
            accessors: JSPropertySpec_AccessorsOrValue_Accessors {
                getter: JSPropertySpec_Accessor {
                    native: JSNativeWrapper {
                        op: None,
                        info: 0 as *const _,
                    },
                },
                setter: JSPropertySpec_Accessor {
                    native: JSNativeWrapper {
                        op: None,
                        info: 0 as *const _,
                    },
                },
            },
        },
    };
}
