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

//! Conversions of Rust values to and from `JSVal`.
//!
//! | IDL type                | Type                             |
//! |-------------------------|----------------------------------|
//! | any                     | `JSVal`                          |
//! | boolean                 | `bool`                           |
//! | byte                    | `i8`                             |
//! | octet                   | `u8`                             |
//! | short                   | `i16`                            |
//! | unsigned short          | `u16`                            |
//! | long                    | `i32`                            |
//! | unsigned long           | `u32`                            |
//! | long long               | `i64`                            |
//! | unsigned long long      | `u64`                            |
//! | unrestricted float      | `f32`                            |
//! | float                   | `Finite<f32>`                    |
//! | unrestricted double     | `f64`                            |
//! | double                  | `Finite<f64>`                    |
//! | USVString               | `String`                         |
//! | object                  | `*mut JSObject`                  |
//! | symbol                  | `*mut Symbol`                    |
//! | nullable types          | `Option<T>`                      |
//! | sequences               | `Vec<T>`                         |

#![deny(missing_docs)]

use crate::error::throw_type_error;
use crate::jsapi::NewArrayObject1;
use crate::jsapi::JS;
use crate::jsapi::{Heap, JS_DefineElement};
use crate::jsapi::{JSContext, JSObject, JSString};
use crate::jsapi::{JS_DeprecatedStringHasLatin1Chars, JS_NewStringCopyUTF8N, JSPROP_ENUMERATE};
use crate::jsval::{BooleanValue, DoubleValue, Int32Value, NullValue, UInt32Value, UndefinedValue};
use crate::jsval::{JSVal, ObjectOrNullValue, ObjectValue, StringValue, SymbolValue};
use crate::rooted;
use crate::rust::for_of;
use crate::rust::maybe_wrap_value;
use crate::rust::wrappers2::{
    AssertSameCompartment, JS_GetLatin1StringCharsAndLength, JS_GetTwoByteStringCharsAndLength,
};
use crate::rust::ForOfIterationFailure;
use crate::rust::{maybe_wrap_object_or_null_value, maybe_wrap_object_value, ToString};
use crate::rust::{HandleValue, MutableHandleValue};
use crate::rust::{ToBoolean, ToInt32, ToInt64, ToNumber, ToUint16, ToUint32, ToUint64};
use libc;
use log::debug;
use num_traits::PrimInt;
use std::borrow::Cow;
use std::ffi::CStr;
use std::ops::ControlFlow;
use std::ptr::NonNull;
use std::rc::Rc;
use std::{ptr, slice};

trait As<O>: Copy {
    fn cast(self) -> O;
}

macro_rules! impl_as {
    ($I:ty, $O:ty) => {
        impl As<$O> for $I {
            fn cast(self) -> $O {
                self as $O
            }
        }
    };
}

impl_as!(f64, u8);
impl_as!(f64, u16);
impl_as!(f64, u32);
impl_as!(f64, u64);
impl_as!(f64, i8);
impl_as!(f64, i16);
impl_as!(f64, i32);
impl_as!(f64, i64);

impl_as!(u8, f64);
impl_as!(u16, f64);
impl_as!(u32, f64);
impl_as!(u64, f64);
impl_as!(i8, f64);
impl_as!(i16, f64);
impl_as!(i32, f64);
impl_as!(i64, f64);

impl_as!(i32, i8);
impl_as!(i32, u8);
impl_as!(i32, i16);
impl_as!(u16, u16);
impl_as!(i32, i32);
impl_as!(u32, u32);
impl_as!(i64, i64);
impl_as!(u64, u64);

/// Similar to num_traits, but we use need to be able to customize values
pub trait Number {
    /// Zero value of this type
    const ZERO: Self;
    /// Smallest finite number this type can represent
    const MIN: Self;
    /// Largest finite number this type can represent
    const MAX: Self;
}

macro_rules! impl_num {
    ($N:ty, $zero:expr, $min:expr, $max:expr) => {
        impl Number for $N {
            const ZERO: $N = $zero;
            const MIN: $N = $min;
            const MAX: $N = $max;
        }
    };
}

// lower upper bound per: https://webidl.spec.whatwg.org/#abstract-opdef-converttoint
impl_num!(u8, 0, u8::MIN, u8::MAX);
impl_num!(u16, 0, u16::MIN, u16::MAX);
impl_num!(u32, 0, u32::MIN, u32::MAX);
impl_num!(u64, 0, 0, (1 << 53) - 1);

impl_num!(i8, 0, i8::MIN, i8::MAX);
impl_num!(i16, 0, i16::MIN, i16::MAX);
impl_num!(i32, 0, i32::MIN, i32::MAX);
impl_num!(i64, 0, -(1 << 53) + 1, (1 << 53) - 1);

impl_num!(f32, 0.0, f32::MIN, f32::MAX);
impl_num!(f64, 0.0, f64::MIN, f64::MAX);

/// A trait to convert Rust types to `JSVal`s.
pub trait ToJSValConvertible {
    /// Convert `self` to a `JSVal`. JSAPI failure causes a panic.
    unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue);

    /// Convert `self` to a `JSVal`. JSAPI failure causes a panic.
    fn safe_to_jsval(&self, cx: &mut crate::context::JSContext, rval: MutableHandleValue) {
        unsafe { self.to_jsval(cx.raw_cx(), rval) }
    }
}

/// An enum to better support enums through FromJSValConvertible::from_jsval.
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum ConversionResult<T> {
    /// Everything went fine.
    Success(T),
    /// Conversion failed, without a pending exception.
    Failure(Cow<'static, CStr>),
}

impl<T> ConversionResult<T> {
    /// Returns Some(value) if it is `ConversionResult::Success`.
    pub fn get_success_value(&self) -> Option<&T> {
        match *self {
            ConversionResult::Success(ref v) => Some(v),
            _ => None,
        }
    }
}

/// A trait to convert `JSVal`s to Rust types.
pub trait FromJSValConvertible: Sized {
    /// Optional configurable behaviour switch; use () for no configuration.
    type Config;

    /// 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.
    /// If it returns `Ok(Failure(reason))`, there is no pending JSAPI exception.
    fn safe_from_jsval(
        cx: &mut crate::context::JSContext,
        val: HandleValue,
        option: Self::Config,
    ) -> Result<ConversionResult<Self>, ()>;
}

/// A trait to convert `JSVal`s to Rust types inside of Rc wrappers.
pub trait FromJSValConvertibleRc: Sized {
    /// Convert `val` to type `Self`.
    /// If it returns `Err(())`, a JSAPI exception is pending.
    /// If it returns `Ok(Failure(reason))`, there is no pending JSAPI exception.
    fn safe_from_jsval(
        cx: &mut crate::context::JSContext,
        val: HandleValue,
    ) -> Result<ConversionResult<Rc<Self>>, ()>;
}

impl<T: FromJSValConvertibleRc> FromJSValConvertible for Rc<T> {
    type Config = ();

    fn safe_from_jsval(
        cx: &mut crate::context::JSContext,
        val: HandleValue,
        _option: (),
    ) -> Result<ConversionResult<Rc<T>>, ()> {
        <T as FromJSValConvertibleRc>::safe_from_jsval(cx, val)
    }
}

/// Behavior for converting out-of-range integers.
#[derive(PartialEq, Eq, Clone)]
pub enum ConversionBehavior {
    /// Wrap into the integer's range.
    Default,
    /// Throw an exception.
    EnforceRange,
    /// Clamp into the integer's range.
    Clamp,
}

/// Try to cast the number to a smaller type, but
/// if it doesn't fit, it will return an error.
// https://searchfox.org/mozilla-esr128/rev/1aa97f9d67f7a7231e62af283eaa02a6b31380e1/dom/bindings/PrimitiveConversions.h#166
fn enforce_range<D>(cx: &mut crate::context::JSContext, d: f64) -> Result<ConversionResult<D>, ()>
where
    D: Number + As<f64>,
    f64: As<D>,
{
    if d.is_infinite() {
        unsafe {
            throw_type_error(
                cx.raw_cx(),
                c"value out of range in an EnforceRange argument",
            )
        };
        return Err(());
    }

    let rounded = d.signum() * d.abs().floor();
    if D::MIN.cast() <= rounded && rounded <= D::MAX.cast() {
        Ok(ConversionResult::Success(rounded.cast()))
    } else {
        unsafe {
            throw_type_error(
                cx.raw_cx(),
                c"value out of range in an EnforceRange argument",
            )
        };
        Err(())
    }
}

/// WebIDL ConvertToInt (Clamp) conversion.
/// Spec: <https://webidl.spec.whatwg.org/#abstract-opdef-converttoint>
///
/// This function is ported from Gecko’s
/// [`PrimitiveConversionTraits_Clamp`](https://searchfox.org/firefox-main/rev/aee7c0f24f488cd7f5a835803b48dd0c0cb2fd5f/dom/bindings/PrimitiveConversions.h#226).
///
/// # Warning
/// This function must only be used when the target type `D` represents an
/// integer WebIDL type. Using it with non-integer types would be incorrect.
fn clamp_to<D>(d: f64) -> D
where
    D: Number + PrimInt + As<f64>,
    f64: As<D>,
{
    // NaN maps to zero.
    if d.is_nan() {
        return D::ZERO;
    }

    if d >= D::MAX.cast() {
        return D::MAX;
    }
    if d <= D::MIN.cast() {
        return D::MIN;
    }

    debug_assert!(d.is_finite());

    // Banker's rounding (round ties towards even).
    // We move away from 0 by 0.5 and then truncate. That gets us the right
    // answer for any starting value except plus or minus N.5. With a starting
    // value of that form, we now have plus or minus N+1. If N is odd, this is
    // the correct result. If N is even, plus or minus N is the correct result.
    let to_truncate = if d < 0.0 { d - 0.5 } else { d + 0.5 };

    let mut truncated: D = to_truncate.cast();

    if truncated.cast() == to_truncate {
        // It was a tie (since moving away from 0 by 0.5 gave us the exact integer
        // we want). Since we rounded away from 0, we either already have an even
        // number or we have an odd number but the number we want is one closer to
        // 0. So just unconditionally masking out the ones bit should do the trick
        // to get us the value we want.
        truncated = truncated & !D::one();
    }

    truncated
}

// https://heycam.github.io/webidl/#es-void
impl ToJSValConvertible for () {
    #[inline]
    unsafe fn to_jsval(&self, _cx: *mut JSContext, mut rval: MutableHandleValue) {
        rval.set(UndefinedValue());
    }
}

impl FromJSValConvertible for JSVal {
    type Config = ();

    fn safe_from_jsval(
        _cx: &mut crate::context::JSContext,
        value: HandleValue,
        _option: (),
    ) -> Result<ConversionResult<JSVal>, ()> {
        Ok(ConversionResult::Success(value.get()))
    }
}

impl ToJSValConvertible for JSVal {
    #[inline]
    unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) {
        rval.set(*self);
        maybe_wrap_value(cx, rval);
    }
}

impl<'a> ToJSValConvertible for HandleValue<'a> {
    #[inline]
    unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) {
        rval.set(self.get());
        maybe_wrap_value(cx, rval);
    }
}

impl ToJSValConvertible for Heap<JSVal> {
    #[inline]
    unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) {
        rval.set(self.get());
        maybe_wrap_value(cx, rval);
    }
}

#[inline]
fn convert_int_from_jsval<T, M>(
    cx: &mut crate::context::JSContext,
    value: HandleValue,
    option: ConversionBehavior,
    convert_fn: unsafe fn(*mut JSContext, HandleValue) -> Result<M, ()>,
) -> Result<ConversionResult<T>, ()>
where
    T: Number + As<f64> + PrimInt,
    M: Number + As<T>,
    f64: As<T>,
{
    match option {
        ConversionBehavior::Default => Ok(ConversionResult::Success(unsafe {
            convert_fn(cx.raw_cx(), value)?.cast()
        })),
        ConversionBehavior::EnforceRange => {
            let number = unsafe { ToNumber(cx.raw_cx(), value) }?;
            enforce_range(cx, number)
        }
        ConversionBehavior::Clamp => Ok(ConversionResult::Success(clamp_to(unsafe {
            ToNumber(cx.raw_cx(), value)
        }?))),
    }
}

// https://heycam.github.io/webidl/#es-boolean
impl ToJSValConvertible for bool {
    #[inline]
    unsafe fn to_jsval(&self, _cx: *mut JSContext, mut rval: MutableHandleValue) {
        rval.set(BooleanValue(*self));
    }
}

// https://heycam.github.io/webidl/#es-boolean
impl FromJSValConvertible for bool {
    type Config = ();

    fn safe_from_jsval(
        _cx: &mut crate::context::JSContext,
        val: HandleValue,
        _option: (),
    ) -> Result<ConversionResult<bool>, ()> {
        unsafe { Ok(ToBoolean(val)).map(ConversionResult::Success) }
    }
}

// https://heycam.github.io/webidl/#es-byte
impl ToJSValConvertible for i8 {
    #[inline]
    unsafe fn to_jsval(&self, _cx: *mut JSContext, mut rval: MutableHandleValue) {
        rval.set(Int32Value(*self as i32));
    }
}

// https://heycam.github.io/webidl/#es-byte
impl FromJSValConvertible for i8 {
    type Config = ConversionBehavior;

    fn safe_from_jsval(
        cx: &mut crate::context::JSContext,
        val: HandleValue,
        option: ConversionBehavior,
    ) -> Result<ConversionResult<i8>, ()> {
        convert_int_from_jsval(cx, val, option, ToInt32)
    }
}

// https://heycam.github.io/webidl/#es-octet
impl ToJSValConvertible for u8 {
    #[inline]
    unsafe fn to_jsval(&self, _cx: *mut JSContext, mut rval: MutableHandleValue) {
        rval.set(Int32Value(*self as i32));
    }
}

// https://heycam.github.io/webidl/#es-octet
impl FromJSValConvertible for u8 {
    type Config = ConversionBehavior;

    fn safe_from_jsval(
        cx: &mut crate::context::JSContext,
        val: HandleValue,
        option: ConversionBehavior,
    ) -> Result<ConversionResult<u8>, ()> {
        convert_int_from_jsval(cx, val, option, ToInt32)
    }
}

// https://heycam.github.io/webidl/#es-short
impl ToJSValConvertible for i16 {
    #[inline]
    unsafe fn to_jsval(&self, _cx: *mut JSContext, mut rval: MutableHandleValue) {
        rval.set(Int32Value(*self as i32));
    }
}

// https://heycam.github.io/webidl/#es-short
impl FromJSValConvertible for i16 {
    type Config = ConversionBehavior;

    fn safe_from_jsval(
        cx: &mut crate::context::JSContext,
        val: HandleValue,
        option: ConversionBehavior,
    ) -> Result<ConversionResult<i16>, ()> {
        convert_int_from_jsval(cx, val, option, ToInt32)
    }
}

// https://heycam.github.io/webidl/#es-unsigned-short
impl ToJSValConvertible for u16 {
    #[inline]
    unsafe fn to_jsval(&self, _cx: *mut JSContext, mut rval: MutableHandleValue) {
        rval.set(Int32Value(*self as i32));
    }
}

// https://heycam.github.io/webidl/#es-unsigned-short
impl FromJSValConvertible for u16 {
    type Config = ConversionBehavior;

    fn safe_from_jsval(
        cx: &mut crate::context::JSContext,
        val: HandleValue,
        option: ConversionBehavior,
    ) -> Result<ConversionResult<u16>, ()> {
        convert_int_from_jsval(cx, val, option, ToUint16)
    }
}

// https://heycam.github.io/webidl/#es-long
impl ToJSValConvertible for i32 {
    #[inline]
    unsafe fn to_jsval(&self, _cx: *mut JSContext, mut rval: MutableHandleValue) {
        rval.set(Int32Value(*self));
    }
}

// https://heycam.github.io/webidl/#es-long
impl FromJSValConvertible for i32 {
    type Config = ConversionBehavior;

    fn safe_from_jsval(
        cx: &mut crate::context::JSContext,
        val: HandleValue,
        option: ConversionBehavior,
    ) -> Result<ConversionResult<i32>, ()> {
        convert_int_from_jsval(cx, val, option, ToInt32)
    }
}

// https://heycam.github.io/webidl/#es-unsigned-long
impl ToJSValConvertible for u32 {
    #[inline]
    unsafe fn to_jsval(&self, _cx: *mut JSContext, mut rval: MutableHandleValue) {
        rval.set(UInt32Value(*self));
    }
}

// https://heycam.github.io/webidl/#es-unsigned-long
impl FromJSValConvertible for u32 {
    type Config = ConversionBehavior;

    fn safe_from_jsval(
        cx: &mut crate::context::JSContext,
        val: HandleValue,
        option: ConversionBehavior,
    ) -> Result<ConversionResult<u32>, ()> {
        convert_int_from_jsval(cx, val, option, ToUint32)
    }
}

// https://heycam.github.io/webidl/#es-long-long
impl ToJSValConvertible for i64 {
    #[inline]
    unsafe fn to_jsval(&self, _cx: *mut JSContext, mut rval: MutableHandleValue) {
        rval.set(DoubleValue(*self as f64));
    }
}

// https://heycam.github.io/webidl/#es-long-long
impl FromJSValConvertible for i64 {
    type Config = ConversionBehavior;

    fn safe_from_jsval(
        cx: &mut crate::context::JSContext,
        val: HandleValue,
        option: ConversionBehavior,
    ) -> Result<ConversionResult<i64>, ()> {
        convert_int_from_jsval(cx, val, option, ToInt64)
    }
}

// https://heycam.github.io/webidl/#es-unsigned-long-long
impl ToJSValConvertible for u64 {
    #[inline]
    unsafe fn to_jsval(&self, _cx: *mut JSContext, mut rval: MutableHandleValue) {
        rval.set(DoubleValue(*self as f64));
    }
}

// https://heycam.github.io/webidl/#es-unsigned-long-long
impl FromJSValConvertible for u64 {
    type Config = ConversionBehavior;

    fn safe_from_jsval(
        cx: &mut crate::context::JSContext,
        val: HandleValue,
        option: ConversionBehavior,
    ) -> Result<ConversionResult<u64>, ()> {
        convert_int_from_jsval(cx, val, option, ToUint64)
    }
}

// https://heycam.github.io/webidl/#es-float
impl ToJSValConvertible for f32 {
    #[inline]
    unsafe fn to_jsval(&self, _cx: *mut JSContext, mut rval: MutableHandleValue) {
        rval.set(DoubleValue(*self as f64));
    }
}

// https://heycam.github.io/webidl/#es-float
impl FromJSValConvertible for f32 {
    type Config = ();

    fn safe_from_jsval(
        cx: &mut crate::context::JSContext,
        val: HandleValue,
        _option: (),
    ) -> Result<ConversionResult<f32>, ()> {
        let result = unsafe { ToNumber(cx.raw_cx(), val) };
        result.map(|f| f as f32).map(ConversionResult::Success)
    }
}

// https://heycam.github.io/webidl/#es-double
impl ToJSValConvertible for f64 {
    #[inline]
    unsafe fn to_jsval(&self, _cx: *mut JSContext, mut rval: MutableHandleValue) {
        rval.set(DoubleValue(*self))
    }
}

// https://heycam.github.io/webidl/#es-double
impl FromJSValConvertible for f64 {
    type Config = ();

    fn safe_from_jsval(
        cx: &mut crate::context::JSContext,
        val: HandleValue,
        _option: (),
    ) -> Result<ConversionResult<f64>, ()> {
        unsafe { ToNumber(cx.raw_cx(), val).map(ConversionResult::Success) }
    }
}

/// Converts a `JSString`, encoded in "Latin1" (i.e. U+0000-U+00FF encoded as 0x00-0xFF) into a
/// `String`.
///
/// ### Safety
/// `s` must points to a valid `JSString`
pub unsafe fn latin1_to_string(cx: &crate::context::JSContext, s: NonNull<JSString>) -> String {
    assert!(unsafe { JS_DeprecatedStringHasLatin1Chars(s.as_ptr()) });

    let mut length = 0;
    let chars = unsafe {
        let chars = JS_GetLatin1StringCharsAndLength(cx, s.as_ptr(), &mut length);
        assert!(!chars.is_null());

        slice::from_raw_parts(chars, length as usize)
    };
    // The `encoding.rs` documentation for `convert_latin1_to_utf8` states that:
    // > The length of the destination buffer must be at least the length of the source
    // > buffer times two.
    let mut v = vec![0; chars.len() * 2];
    let real_size = encoding_rs::mem::convert_latin1_to_utf8(chars, v.as_mut_slice());

    v.truncate(real_size);

    // Safety: convert_latin1_to_utf8 converts the raw bytes to utf8 and the
    // buffer is the size specified in the documentation, so this should be safe.
    unsafe { String::from_utf8_unchecked(v) }
}

/// Converts a `JSString` into a `String`, regardless of used encoding.
///
/// ### Safety
/// `jsstr` must points to a valid `JSString`
pub unsafe fn jsstr_to_string(cx: &crate::context::JSContext, jsstr: NonNull<JSString>) -> String {
    if unsafe { JS_DeprecatedStringHasLatin1Chars(jsstr.as_ptr()) } {
        return latin1_to_string(cx, jsstr);
    }

    let mut length = 0;
    let chars = unsafe { JS_GetTwoByteStringCharsAndLength(cx, jsstr.as_ptr(), &mut length) };
    assert!(!chars.is_null());
    let char_vec = unsafe { slice::from_raw_parts(chars, length as usize) };
    String::from_utf16_lossy(char_vec)
}

/// Converts a `JSString`, encoded in "Latin1" (i.e. U+0000-U+00FF encoded as 0x00-0xFF) into a
/// `String`.
///
/// Use [`latin1_to_string`] if possible as this function will be eventually removed.
#[deprecated(note = "Use latin1_to_string instead")]
pub unsafe fn unsafe_latin1_to_string(cx: *mut JSContext, s: NonNull<JSString>) -> String {
    // while this can break direct invariants of JSContext
    // it is ok in the current usage of this function and it avoids duplicating the code
    let cx = crate::context::JSContext::from_ptr(NonNull::new(cx).unwrap());
    latin1_to_string(&cx, s)
}

/// Converts a `JSString` into a `String`, regardless of used encoding.
///
/// Use [`jsstr_to_string`] if possible as this function will be eventually removed.
#[deprecated(note = "Use jsstr_to_string instead")]
pub unsafe fn unsafe_jsstr_to_string(cx: *mut JSContext, jsstr: NonNull<JSString>) -> String {
    // while this can break direct invariants of JSContext
    // it is ok in the current usage of this function and it avoids duplicating the code
    let cx = crate::context::JSContext::from_ptr(NonNull::new(cx).unwrap());
    jsstr_to_string(&cx, jsstr)
}

// https://heycam.github.io/webidl/#es-USVString
impl ToJSValConvertible for str {
    #[inline]
    #[deny(unsafe_op_in_unsafe_fn)]
    unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) {
        // Spidermonkey will automatically only copy latin1
        // or similar if the given encoding can be small enough.
        // So there is no need to distinguish between ascii only or similar.
        let s = Utf8Chars::from(self);
        let jsstr = unsafe { JS_NewStringCopyUTF8N(cx, &*s as *const _) };
        if jsstr.is_null() {
            panic!("JS String copy routine failed");
        }
        unsafe {
            rval.set(StringValue(&*jsstr));
        }
    }
}

// https://heycam.github.io/webidl/#es-USVString
impl ToJSValConvertible for String {
    #[inline]
    unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
        (**self).to_jsval(cx, rval);
    }
}

// https://heycam.github.io/webidl/#es-USVString
impl FromJSValConvertible for String {
    type Config = ();

    fn safe_from_jsval(
        cx: &mut crate::context::JSContext,
        value: HandleValue,
        _config: Self::Config,
    ) -> Result<ConversionResult<String>, ()> {
        let jsstr = unsafe { ToString(cx, value) };
        let Some(jsstr) = NonNull::new(jsstr) else {
            debug!("ToString failed");
            return Err(());
        };
        Ok(unsafe { jsstr_to_string(cx, jsstr) }).map(ConversionResult::Success)
    }
}

impl<T: ToJSValConvertible> ToJSValConvertible for Option<T> {
    #[inline]
    unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) {
        match self {
            &Some(ref value) => value.to_jsval(cx, rval),
            &None => rval.set(NullValue()),
        }
    }
}

impl<T: FromJSValConvertible> FromJSValConvertible for Option<T> {
    type Config = T::Config;

    fn safe_from_jsval(
        cx: &mut crate::context::JSContext,
        value: HandleValue,
        option: T::Config,
    ) -> Result<ConversionResult<Option<T>>, ()> {
        if value.get().is_null_or_undefined() {
            Ok(ConversionResult::Success(None))
        } else {
            Ok(
                match FromJSValConvertible::safe_from_jsval(cx, value, option)? {
                    ConversionResult::Success(v) => ConversionResult::Success(Some(v)),
                    ConversionResult::Failure(v) => ConversionResult::Failure(v),
                },
            )
        }
    }
}

impl<T: ToJSValConvertible> ToJSValConvertible for &'_ T {
    #[inline]
    unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
        (**self).to_jsval(cx, rval)
    }
}

impl<T: ToJSValConvertible> ToJSValConvertible for Box<T> {
    #[inline]
    unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
        (**self).to_jsval(cx, rval)
    }
}

impl<T: ToJSValConvertible> ToJSValConvertible for Rc<T> {
    #[inline]
    unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
        (**self).to_jsval(cx, rval)
    }
}

// https://heycam.github.io/webidl/#es-sequence
impl<T: ToJSValConvertible> ToJSValConvertible for [T] {
    #[inline]
    unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) {
        rooted!(in(cx) let js_array = NewArrayObject1(cx, self.len() as libc::size_t));
        assert!(!js_array.handle().is_null());

        rooted!(in(cx) let mut val = UndefinedValue());
        for (index, obj) in self.iter().enumerate() {
            obj.to_jsval(cx, val.handle_mut());

            assert!(JS_DefineElement(
                cx,
                js_array.handle().into(),
                index as u32,
                val.handle().into(),
                JSPROP_ENUMERATE as u32
            ));
        }

        rval.set(ObjectValue(js_array.handle().get()));
    }
}

// https://heycam.github.io/webidl/#es-sequence
impl<T: ToJSValConvertible> ToJSValConvertible for Vec<T> {
    #[inline]
    unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
        <[_]>::to_jsval(self, cx, rval)
    }
}

impl<C: Clone, T: FromJSValConvertible<Config = C>> FromJSValConvertible for Vec<T> {
    type Config = C;

    fn safe_from_jsval(
        cx: &mut crate::context::JSContext,
        value: HandleValue,
        option: C,
    ) -> Result<ConversionResult<Vec<T>>, ()> {
        if !value.is_object() {
            return Ok(ConversionResult::Failure(c"Value is not an object".into()));
        }

        let mut return_value = vec![];
        let result = for_of(unsafe { cx.raw_cx() }, value, |iterator_element| {
            let conversion_result = T::safe_from_jsval(cx, iterator_element, option.clone())
                .map_err(|_| ForOfIterationFailure::JSFailed)?;
            return_value.push(match conversion_result {
                ConversionResult::Success(value) => value,
                ConversionResult::Failure(error) => {
                    return Err(ForOfIterationFailure::Other(error));
                }
            });

            Ok(ControlFlow::Continue(()))
        });

        match result {
            Ok(_) => Ok(ConversionResult::Success(return_value)),
            Err(ForOfIterationFailure::ValueIsNotIterable) => {
                Ok(ConversionResult::Failure(c"Value is not iterable".into()))
            }
            Err(ForOfIterationFailure::JSFailed) => Err(()),
            Err(ForOfIterationFailure::Other(error)) => {
                unsafe { throw_type_error(cx.raw_cx(), error.as_ref()) };
                Err(())
            }
        }
    }
}

// https://heycam.github.io/webidl/#es-object
impl ToJSValConvertible for *mut JSObject {
    #[inline]
    unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) {
        rval.set(ObjectOrNullValue(*self));
        maybe_wrap_object_or_null_value(cx, rval);
    }
}

// https://heycam.github.io/webidl/#es-object
impl ToJSValConvertible for ptr::NonNull<JSObject> {
    #[inline]
    unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) {
        rval.set(ObjectValue(self.as_ptr()));
        maybe_wrap_object_value(cx, rval);
    }
}

// https://heycam.github.io/webidl/#es-object
impl ToJSValConvertible for Heap<*mut JSObject> {
    #[inline]
    unsafe fn to_jsval(&self, cx: *mut JSContext, mut rval: MutableHandleValue) {
        rval.set(ObjectOrNullValue(self.get()));
        maybe_wrap_object_or_null_value(cx, rval);
    }
}

// https://heycam.github.io/webidl/#es-object
impl FromJSValConvertible for *mut JSObject {
    type Config = ();

    #[inline]
    fn safe_from_jsval(
        cx: &mut crate::context::JSContext,
        value: HandleValue,
        _option: (),
    ) -> Result<ConversionResult<*mut JSObject>, ()> {
        if !value.is_object() {
            unsafe {
                throw_type_error(cx.raw_cx(), c"value is not an object");
            }
            return Err(());
        }

        unsafe { AssertSameCompartment(cx, value.to_object()) };

        Ok(ConversionResult::Success(value.to_object()))
    }
}

impl ToJSValConvertible for *mut JS::Symbol {
    #[inline]
    unsafe fn to_jsval(&self, _: *mut JSContext, mut rval: MutableHandleValue) {
        rval.set(SymbolValue(&**self));
    }
}

impl FromJSValConvertible for *mut JS::Symbol {
    type Config = ();

    #[inline]
    fn safe_from_jsval(
        cx: &mut crate::context::JSContext,
        value: HandleValue,
        _option: (),
    ) -> Result<ConversionResult<*mut JS::Symbol>, ()> {
        if !value.is_symbol() {
            unsafe {
                throw_type_error(cx.raw_cx(), c"value is not a symbol");
            }
            return Err(());
        }

        Ok(ConversionResult::Success(value.to_symbol()))
    }
}

/// A wrapper type over [`crate::jsapi::UTF8Chars`]. This is created to help transferring
/// a rust string to mozjs. The inner [`crate::jsapi::UTF8Chars`] can be accessed via the
/// [`std::ops::Deref`] trait.
pub struct Utf8Chars<'a> {
    lt_marker: std::marker::PhantomData<&'a ()>,
    inner: crate::jsapi::UTF8Chars,
}

impl<'a> std::ops::Deref for Utf8Chars<'a> {
    type Target = crate::jsapi::UTF8Chars;

    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

impl<'a> From<&'a str> for Utf8Chars<'a> {
    #[allow(unsafe_code)]
    fn from(value: &'a str) -> Self {
        use std::marker::PhantomData;

        use crate::jsapi::mozilla::{Range, RangedPtr};
        use crate::jsapi::UTF8Chars;

        let range = value.as_bytes().as_ptr_range();
        let range_start = range.start as *mut _;
        let range_end = range.end as *mut _;
        let start = RangedPtr {
            _phantom_0: PhantomData,
            mPtr: range_start,
            #[cfg(feature = "debugmozjs")]
            mRangeStart: range_start,
            #[cfg(feature = "debugmozjs")]
            mRangeEnd: range_end,
        };
        let end = RangedPtr {
            _phantom_0: PhantomData,
            mPtr: range_end,
            #[cfg(feature = "debugmozjs")]
            mRangeStart: range_start,
            #[cfg(feature = "debugmozjs")]
            mRangeEnd: range_end,
        };
        let base = Range {
            _phantom_0: PhantomData,
            mStart: start,
            mEnd: end,
        };
        let inner = UTF8Chars { _base: base };
        Self {
            lt_marker: PhantomData,
            inner,
        }
    }
}
