Skip to main content

mozjs/
conversions.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5//! Conversions of Rust values to and from `JSVal`.
6//!
7//! | IDL type                | Type                             |
8//! |-------------------------|----------------------------------|
9//! | any                     | `JSVal`                          |
10//! | boolean                 | `bool`                           |
11//! | byte                    | `i8`                             |
12//! | octet                   | `u8`                             |
13//! | short                   | `i16`                            |
14//! | unsigned short          | `u16`                            |
15//! | long                    | `i32`                            |
16//! | unsigned long           | `u32`                            |
17//! | long long               | `i64`                            |
18//! | unsigned long long      | `u64`                            |
19//! | unrestricted float      | `f32`                            |
20//! | float                   | `Finite<f32>`                    |
21//! | unrestricted double     | `f64`                            |
22//! | double                  | `Finite<f64>`                    |
23//! | USVString               | `String`                         |
24//! | object                  | `*mut JSObject`                  |
25//! | symbol                  | `*mut Symbol`                    |
26//! | nullable types          | `Option<T>`                      |
27//! | sequences               | `Vec<T>`                         |
28
29#![deny(missing_docs)]
30
31use crate::context::JSContext;
32use crate::error::throw_type_error;
33use crate::jsapi::Heap;
34use crate::jsapi::JS;
35use crate::jsapi::{JSObject, JSString};
36use crate::jsapi::{JS_DeprecatedStringHasLatin1Chars, JSPROP_ENUMERATE};
37use crate::jsval::{BooleanValue, DoubleValue, Int32Value, NullValue, UInt32Value, UndefinedValue};
38use crate::jsval::{JSVal, ObjectOrNullValue, ObjectValue, StringValue, SymbolValue};
39use crate::rooted;
40use crate::rust::for_of;
41use crate::rust::maybe_wrap_value;
42use crate::rust::wrappers2::{
43    AssertSameCompartment, JS_DefineElement, JS_GetLatin1StringCharsAndLength,
44    JS_GetTwoByteStringCharsAndLength, JS_NewStringCopyUTF8N, NewArrayObject1,
45};
46use crate::rust::ForOfIterationFailure;
47use crate::rust::{maybe_wrap_object_or_null_value, maybe_wrap_object_value, ToString};
48use crate::rust::{HandleValue, MutableHandleValue};
49use crate::rust::{ToBoolean, ToInt32, ToInt64, ToNumber, ToUint16, ToUint32, ToUint64};
50use libc;
51use log::debug;
52use num_traits::PrimInt;
53use std::borrow::Cow;
54use std::ffi::CStr;
55use std::ops::ControlFlow;
56use std::ptr::NonNull;
57use std::rc::Rc;
58use std::{ptr, slice};
59
60trait As<O>: Copy {
61    fn cast(self) -> O;
62}
63
64macro_rules! impl_as {
65    ($I:ty, $O:ty) => {
66        impl As<$O> for $I {
67            fn cast(self) -> $O {
68                self as $O
69            }
70        }
71    };
72}
73
74impl_as!(f64, u8);
75impl_as!(f64, u16);
76impl_as!(f64, u32);
77impl_as!(f64, u64);
78impl_as!(f64, i8);
79impl_as!(f64, i16);
80impl_as!(f64, i32);
81impl_as!(f64, i64);
82
83impl_as!(u8, f64);
84impl_as!(u16, f64);
85impl_as!(u32, f64);
86impl_as!(u64, f64);
87impl_as!(i8, f64);
88impl_as!(i16, f64);
89impl_as!(i32, f64);
90impl_as!(i64, f64);
91
92impl_as!(i32, i8);
93impl_as!(i32, u8);
94impl_as!(i32, i16);
95impl_as!(u16, u16);
96impl_as!(i32, i32);
97impl_as!(u32, u32);
98impl_as!(i64, i64);
99impl_as!(u64, u64);
100
101/// Similar to num_traits, but we use need to be able to customize values
102pub trait Number {
103    /// Zero value of this type
104    const ZERO: Self;
105    /// Smallest finite number this type can represent
106    const MIN: Self;
107    /// Largest finite number this type can represent
108    const MAX: Self;
109}
110
111macro_rules! impl_num {
112    ($N:ty, $zero:expr, $min:expr, $max:expr) => {
113        impl Number for $N {
114            const ZERO: $N = $zero;
115            const MIN: $N = $min;
116            const MAX: $N = $max;
117        }
118    };
119}
120
121// lower upper bound per: https://webidl.spec.whatwg.org/#abstract-opdef-converttoint
122impl_num!(u8, 0, u8::MIN, u8::MAX);
123impl_num!(u16, 0, u16::MIN, u16::MAX);
124impl_num!(u32, 0, u32::MIN, u32::MAX);
125impl_num!(u64, 0, 0, (1 << 53) - 1);
126
127impl_num!(i8, 0, i8::MIN, i8::MAX);
128impl_num!(i16, 0, i16::MIN, i16::MAX);
129impl_num!(i32, 0, i32::MIN, i32::MAX);
130impl_num!(i64, 0, -(1 << 53) + 1, (1 << 53) - 1);
131
132impl_num!(f32, 0.0, f32::MIN, f32::MAX);
133impl_num!(f64, 0.0, f64::MIN, f64::MAX);
134
135/// A trait to convert Rust types to `JSVal`s.
136pub trait ToJSValConvertible {
137    /// Convert `self` to a `JSVal`. JSAPI failure causes a panic.
138    fn to_jsval(&self, cx: &mut JSContext, rval: MutableHandleValue);
139}
140
141/// An enum to better support enums through FromJSValConvertible::from_jsval.
142#[derive(PartialEq, Eq, Clone, Debug)]
143pub enum ConversionResult<T> {
144    /// Everything went fine.
145    Success(T),
146    /// Conversion failed, without a pending exception.
147    Failure(Cow<'static, CStr>),
148}
149
150impl<T> ConversionResult<T> {
151    /// Returns Some(value) if it is `ConversionResult::Success`.
152    pub fn get_success_value(&self) -> Option<&T> {
153        match *self {
154            ConversionResult::Success(ref v) => Some(v),
155            _ => None,
156        }
157    }
158}
159
160/// A trait to convert `JSVal`s to Rust types.
161pub trait FromJSValConvertible: Sized {
162    /// Optional configurable behaviour switch; use () for no configuration.
163    type Config;
164
165    /// Convert `val` to type `Self`.
166    /// Optional configuration of type `T` can be passed as the `option`
167    /// argument.
168    /// If it returns `Err(())`, a JSAPI exception is pending.
169    /// If it returns `Ok(Failure(reason))`, there is no pending JSAPI exception.
170    fn from_jsval(
171        cx: &mut JSContext,
172        val: HandleValue,
173        option: Self::Config,
174    ) -> Result<ConversionResult<Self>, ()>;
175}
176
177/// A trait to convert `JSVal`s to Rust types inside of Rc wrappers.
178pub trait FromJSValConvertibleRc: Sized {
179    /// Convert `val` to type `Self`.
180    /// If it returns `Err(())`, a JSAPI exception is pending.
181    /// If it returns `Ok(Failure(reason))`, there is no pending JSAPI exception.
182    fn from_jsval(cx: &mut JSContext, val: HandleValue) -> Result<ConversionResult<Rc<Self>>, ()>;
183}
184
185impl<T: FromJSValConvertibleRc> FromJSValConvertible for Rc<T> {
186    type Config = ();
187
188    fn from_jsval(
189        cx: &mut JSContext,
190        val: HandleValue,
191        _option: (),
192    ) -> Result<ConversionResult<Rc<T>>, ()> {
193        <T as FromJSValConvertibleRc>::from_jsval(cx, val)
194    }
195}
196
197/// Behavior for converting out-of-range integers.
198#[derive(PartialEq, Eq, Clone)]
199pub enum ConversionBehavior {
200    /// Wrap into the integer's range.
201    Default,
202    /// Throw an exception.
203    EnforceRange,
204    /// Clamp into the integer's range.
205    Clamp,
206}
207
208/// Try to cast the number to a smaller type, but
209/// if it doesn't fit, it will return an error.
210// https://searchfox.org/mozilla-esr128/rev/1aa97f9d67f7a7231e62af283eaa02a6b31380e1/dom/bindings/PrimitiveConversions.h#166
211fn enforce_range<D>(cx: &mut JSContext, d: f64) -> Result<ConversionResult<D>, ()>
212where
213    D: Number + As<f64>,
214    f64: As<D>,
215{
216    if d.is_infinite() {
217        throw_type_error(cx, c"value out of range in an EnforceRange argument");
218        return Err(());
219    }
220
221    let rounded = d.signum() * d.abs().floor();
222    if D::MIN.cast() <= rounded && rounded <= D::MAX.cast() {
223        Ok(ConversionResult::Success(rounded.cast()))
224    } else {
225        throw_type_error(cx, c"value out of range in an EnforceRange argument");
226        Err(())
227    }
228}
229
230/// WebIDL ConvertToInt (Clamp) conversion.
231/// Spec: <https://webidl.spec.whatwg.org/#abstract-opdef-converttoint>
232///
233/// This function is ported from Gecko’s
234/// [`PrimitiveConversionTraits_Clamp`](https://searchfox.org/firefox-main/rev/aee7c0f24f488cd7f5a835803b48dd0c0cb2fd5f/dom/bindings/PrimitiveConversions.h#226).
235///
236/// # Warning
237/// This function must only be used when the target type `D` represents an
238/// integer WebIDL type. Using it with non-integer types would be incorrect.
239fn clamp_to<D>(d: f64) -> D
240where
241    D: Number + PrimInt + As<f64>,
242    f64: As<D>,
243{
244    // NaN maps to zero.
245    if d.is_nan() {
246        return D::ZERO;
247    }
248
249    if d >= D::MAX.cast() {
250        return D::MAX;
251    }
252    if d <= D::MIN.cast() {
253        return D::MIN;
254    }
255
256    debug_assert!(d.is_finite());
257
258    // Banker's rounding (round ties towards even).
259    // We move away from 0 by 0.5 and then truncate. That gets us the right
260    // answer for any starting value except plus or minus N.5. With a starting
261    // value of that form, we now have plus or minus N+1. If N is odd, this is
262    // the correct result. If N is even, plus or minus N is the correct result.
263    let to_truncate = if d < 0.0 { d - 0.5 } else { d + 0.5 };
264
265    let mut truncated: D = to_truncate.cast();
266
267    if truncated.cast() == to_truncate {
268        // It was a tie (since moving away from 0 by 0.5 gave us the exact integer
269        // we want). Since we rounded away from 0, we either already have an even
270        // number or we have an odd number but the number we want is one closer to
271        // 0. So just unconditionally masking out the ones bit should do the trick
272        // to get us the value we want.
273        truncated = truncated & !D::one();
274    }
275
276    truncated
277}
278
279// https://heycam.github.io/webidl/#es-void
280impl ToJSValConvertible for () {
281    #[inline]
282    fn to_jsval(&self, _cx: &mut JSContext, mut rval: MutableHandleValue) {
283        rval.set(UndefinedValue());
284    }
285}
286
287impl FromJSValConvertible for JSVal {
288    type Config = ();
289
290    fn from_jsval(
291        _cx: &mut JSContext,
292        value: HandleValue,
293        _option: (),
294    ) -> Result<ConversionResult<JSVal>, ()> {
295        Ok(ConversionResult::Success(value.get()))
296    }
297}
298
299impl ToJSValConvertible for JSVal {
300    #[inline]
301    fn to_jsval(&self, cx: &mut JSContext, mut rval: MutableHandleValue) {
302        rval.set(*self);
303        maybe_wrap_value(cx, rval);
304    }
305}
306
307impl<'a> ToJSValConvertible for HandleValue<'a> {
308    #[inline]
309    fn to_jsval(&self, cx: &mut JSContext, mut rval: MutableHandleValue) {
310        rval.set(self.get());
311        maybe_wrap_value(cx, rval);
312    }
313}
314
315impl ToJSValConvertible for Heap<JSVal> {
316    #[inline]
317    fn to_jsval(&self, cx: &mut JSContext, mut rval: MutableHandleValue) {
318        rval.set(self.get());
319        maybe_wrap_value(cx, rval);
320    }
321}
322
323#[inline]
324fn convert_int_from_jsval<T, M>(
325    cx: &mut JSContext,
326    value: HandleValue,
327    option: ConversionBehavior,
328    convert_fn: unsafe fn(&mut JSContext, HandleValue) -> Result<M, ()>,
329) -> Result<ConversionResult<T>, ()>
330where
331    T: Number + As<f64> + PrimInt,
332    M: Number + As<T>,
333    f64: As<T>,
334{
335    match option {
336        ConversionBehavior::Default => Ok(ConversionResult::Success(unsafe {
337            convert_fn(cx, value)?.cast()
338        })),
339        ConversionBehavior::EnforceRange => {
340            let number = unsafe { ToNumber(cx, value) }?;
341            enforce_range(cx, number)
342        }
343        ConversionBehavior::Clamp => Ok(ConversionResult::Success(clamp_to(unsafe {
344            ToNumber(cx, value)
345        }?))),
346    }
347}
348
349// https://heycam.github.io/webidl/#es-boolean
350impl ToJSValConvertible for bool {
351    #[inline]
352    fn to_jsval(&self, _cx: &mut JSContext, mut rval: MutableHandleValue) {
353        rval.set(BooleanValue(*self));
354    }
355}
356
357// https://heycam.github.io/webidl/#es-boolean
358impl FromJSValConvertible for bool {
359    type Config = ();
360
361    fn from_jsval(
362        _cx: &mut JSContext,
363        val: HandleValue,
364        _option: (),
365    ) -> Result<ConversionResult<bool>, ()> {
366        unsafe { Ok(ToBoolean(val)).map(ConversionResult::Success) }
367    }
368}
369
370// https://heycam.github.io/webidl/#es-byte
371impl ToJSValConvertible for i8 {
372    #[inline]
373    fn to_jsval(&self, _cx: &mut JSContext, mut rval: MutableHandleValue) {
374        rval.set(Int32Value(*self as i32));
375    }
376}
377
378// https://heycam.github.io/webidl/#es-byte
379impl FromJSValConvertible for i8 {
380    type Config = ConversionBehavior;
381
382    fn from_jsval(
383        cx: &mut JSContext,
384        val: HandleValue,
385        option: ConversionBehavior,
386    ) -> Result<ConversionResult<i8>, ()> {
387        convert_int_from_jsval(cx, val, option, ToInt32)
388    }
389}
390
391// https://heycam.github.io/webidl/#es-octet
392impl ToJSValConvertible for u8 {
393    #[inline]
394    fn to_jsval(&self, _cx: &mut JSContext, mut rval: MutableHandleValue) {
395        rval.set(Int32Value(*self as i32));
396    }
397}
398
399// https://heycam.github.io/webidl/#es-octet
400impl FromJSValConvertible for u8 {
401    type Config = ConversionBehavior;
402
403    fn from_jsval(
404        cx: &mut JSContext,
405        val: HandleValue,
406        option: ConversionBehavior,
407    ) -> Result<ConversionResult<u8>, ()> {
408        convert_int_from_jsval(cx, val, option, ToInt32)
409    }
410}
411
412// https://heycam.github.io/webidl/#es-short
413impl ToJSValConvertible for i16 {
414    #[inline]
415    fn to_jsval(&self, _cx: &mut JSContext, mut rval: MutableHandleValue) {
416        rval.set(Int32Value(*self as i32));
417    }
418}
419
420// https://heycam.github.io/webidl/#es-short
421impl FromJSValConvertible for i16 {
422    type Config = ConversionBehavior;
423
424    fn from_jsval(
425        cx: &mut JSContext,
426        val: HandleValue,
427        option: ConversionBehavior,
428    ) -> Result<ConversionResult<i16>, ()> {
429        convert_int_from_jsval(cx, val, option, ToInt32)
430    }
431}
432
433// https://heycam.github.io/webidl/#es-unsigned-short
434impl ToJSValConvertible for u16 {
435    #[inline]
436    fn to_jsval(&self, _cx: &mut JSContext, mut rval: MutableHandleValue) {
437        rval.set(Int32Value(*self as i32));
438    }
439}
440
441// https://heycam.github.io/webidl/#es-unsigned-short
442impl FromJSValConvertible for u16 {
443    type Config = ConversionBehavior;
444
445    fn from_jsval(
446        cx: &mut JSContext,
447        val: HandleValue,
448        option: ConversionBehavior,
449    ) -> Result<ConversionResult<u16>, ()> {
450        convert_int_from_jsval(cx, val, option, ToUint16)
451    }
452}
453
454// https://heycam.github.io/webidl/#es-long
455impl ToJSValConvertible for i32 {
456    #[inline]
457    fn to_jsval(&self, _cx: &mut JSContext, mut rval: MutableHandleValue) {
458        rval.set(Int32Value(*self));
459    }
460}
461
462// https://heycam.github.io/webidl/#es-long
463impl FromJSValConvertible for i32 {
464    type Config = ConversionBehavior;
465
466    fn from_jsval(
467        cx: &mut JSContext,
468        val: HandleValue,
469        option: ConversionBehavior,
470    ) -> Result<ConversionResult<i32>, ()> {
471        convert_int_from_jsval(cx, val, option, ToInt32)
472    }
473}
474
475// https://heycam.github.io/webidl/#es-unsigned-long
476impl ToJSValConvertible for u32 {
477    #[inline]
478    fn to_jsval(&self, _cx: &mut JSContext, mut rval: MutableHandleValue) {
479        rval.set(UInt32Value(*self));
480    }
481}
482
483// https://heycam.github.io/webidl/#es-unsigned-long
484impl FromJSValConvertible for u32 {
485    type Config = ConversionBehavior;
486
487    fn from_jsval(
488        cx: &mut JSContext,
489        val: HandleValue,
490        option: ConversionBehavior,
491    ) -> Result<ConversionResult<u32>, ()> {
492        convert_int_from_jsval(cx, val, option, ToUint32)
493    }
494}
495
496// https://heycam.github.io/webidl/#es-long-long
497impl ToJSValConvertible for i64 {
498    #[inline]
499    fn to_jsval(&self, _cx: &mut JSContext, mut rval: MutableHandleValue) {
500        rval.set(DoubleValue(*self as f64));
501    }
502}
503
504// https://heycam.github.io/webidl/#es-long-long
505impl FromJSValConvertible for i64 {
506    type Config = ConversionBehavior;
507
508    fn from_jsval(
509        cx: &mut JSContext,
510        val: HandleValue,
511        option: ConversionBehavior,
512    ) -> Result<ConversionResult<i64>, ()> {
513        convert_int_from_jsval(cx, val, option, ToInt64)
514    }
515}
516
517// https://heycam.github.io/webidl/#es-unsigned-long-long
518impl ToJSValConvertible for u64 {
519    #[inline]
520    fn to_jsval(&self, _cx: &mut JSContext, mut rval: MutableHandleValue) {
521        rval.set(DoubleValue(*self as f64));
522    }
523}
524
525// https://heycam.github.io/webidl/#es-unsigned-long-long
526impl FromJSValConvertible for u64 {
527    type Config = ConversionBehavior;
528
529    fn from_jsval(
530        cx: &mut JSContext,
531        val: HandleValue,
532        option: ConversionBehavior,
533    ) -> Result<ConversionResult<u64>, ()> {
534        convert_int_from_jsval(cx, val, option, ToUint64)
535    }
536}
537
538// https://heycam.github.io/webidl/#es-float
539impl ToJSValConvertible for f32 {
540    #[inline]
541    fn to_jsval(&self, _cx: &mut JSContext, mut rval: MutableHandleValue) {
542        rval.set(DoubleValue(*self as f64));
543    }
544}
545
546// https://heycam.github.io/webidl/#es-float
547impl FromJSValConvertible for f32 {
548    type Config = ();
549
550    fn from_jsval(
551        cx: &mut JSContext,
552        val: HandleValue,
553        _option: (),
554    ) -> Result<ConversionResult<f32>, ()> {
555        let result = unsafe { ToNumber(cx, val) };
556        result.map(|f| f as f32).map(ConversionResult::Success)
557    }
558}
559
560// https://heycam.github.io/webidl/#es-double
561impl ToJSValConvertible for f64 {
562    #[inline]
563    fn to_jsval(&self, _cx: &mut JSContext, mut rval: MutableHandleValue) {
564        rval.set(DoubleValue(*self))
565    }
566}
567
568// https://heycam.github.io/webidl/#es-double
569impl FromJSValConvertible for f64 {
570    type Config = ();
571
572    fn from_jsval(
573        cx: &mut JSContext,
574        val: HandleValue,
575        _option: (),
576    ) -> Result<ConversionResult<f64>, ()> {
577        unsafe { ToNumber(cx, val).map(ConversionResult::Success) }
578    }
579}
580
581/// Converts a `JSString`, encoded in "Latin1" (i.e. U+0000-U+00FF encoded as 0x00-0xFF) into a
582/// `String`.
583///
584/// ### Safety
585/// `s` must points to a valid `JSString`
586pub unsafe fn latin1_to_string(cx: &crate::context::JSContext, s: NonNull<JSString>) -> String {
587    assert!(unsafe { JS_DeprecatedStringHasLatin1Chars(s.as_ptr()) });
588
589    let mut length = 0;
590    let chars = unsafe {
591        let chars = JS_GetLatin1StringCharsAndLength(cx, s.as_ptr(), &mut length);
592        assert!(!chars.is_null());
593
594        slice::from_raw_parts(chars, length as usize)
595    };
596    // The `encoding.rs` documentation for `convert_latin1_to_utf8` states that:
597    // > The length of the destination buffer must be at least the length of the source
598    // > buffer times two.
599    let mut v = vec![0; chars.len() * 2];
600    let real_size = encoding_rs::mem::convert_latin1_to_utf8(chars, v.as_mut_slice());
601
602    v.truncate(real_size);
603
604    // Safety: convert_latin1_to_utf8 converts the raw bytes to utf8 and the
605    // buffer is the size specified in the documentation, so this should be safe.
606    unsafe { String::from_utf8_unchecked(v) }
607}
608
609/// Converts a `JSString` into a `String`, regardless of used encoding.
610///
611/// ### Safety
612/// `jsstr` must points to a valid `JSString`
613pub unsafe fn jsstr_to_string(cx: &crate::context::JSContext, jsstr: NonNull<JSString>) -> String {
614    if unsafe { JS_DeprecatedStringHasLatin1Chars(jsstr.as_ptr()) } {
615        return latin1_to_string(cx, jsstr);
616    }
617
618    let mut length = 0;
619    let chars = unsafe { JS_GetTwoByteStringCharsAndLength(cx, jsstr.as_ptr(), &mut length) };
620    assert!(!chars.is_null());
621    let char_vec = unsafe { slice::from_raw_parts(chars, length as usize) };
622    String::from_utf16_lossy(char_vec)
623}
624
625// https://heycam.github.io/webidl/#es-USVString
626impl ToJSValConvertible for str {
627    #[inline]
628    #[deny(unsafe_op_in_unsafe_fn)]
629    fn to_jsval(&self, cx: &mut JSContext, mut rval: MutableHandleValue) {
630        // Spidermonkey will automatically only copy latin1
631        // or similar if the given encoding can be small enough.
632        // So there is no need to distinguish between ascii only or similar.
633        let s = Utf8Chars::from(self);
634        let jsstr = unsafe { JS_NewStringCopyUTF8N(cx, &*s as *const _) };
635        if jsstr.is_null() {
636            panic!("JS String copy routine failed");
637        }
638        unsafe {
639            rval.set(StringValue(&*jsstr));
640        }
641    }
642}
643
644// https://heycam.github.io/webidl/#es-USVString
645impl ToJSValConvertible for String {
646    #[inline]
647    fn to_jsval(&self, cx: &mut JSContext, rval: MutableHandleValue) {
648        (**self).to_jsval(cx, rval);
649    }
650}
651
652// https://heycam.github.io/webidl/#es-USVString
653impl FromJSValConvertible for String {
654    type Config = ();
655
656    fn from_jsval(
657        cx: &mut JSContext,
658        value: HandleValue,
659        _config: Self::Config,
660    ) -> Result<ConversionResult<String>, ()> {
661        let jsstr = unsafe { ToString(cx, value) };
662        let Some(jsstr) = NonNull::new(jsstr) else {
663            debug!("ToString failed");
664            return Err(());
665        };
666        Ok(unsafe { jsstr_to_string(cx, jsstr) }).map(ConversionResult::Success)
667    }
668}
669
670impl<T: ToJSValConvertible> ToJSValConvertible for Option<T> {
671    #[inline]
672    fn to_jsval(&self, cx: &mut JSContext, mut rval: MutableHandleValue) {
673        match self {
674            &Some(ref value) => value.to_jsval(cx, rval),
675            &None => rval.set(NullValue()),
676        }
677    }
678}
679
680impl<T: FromJSValConvertible> FromJSValConvertible for Option<T> {
681    type Config = T::Config;
682
683    fn from_jsval(
684        cx: &mut JSContext,
685        value: HandleValue,
686        option: T::Config,
687    ) -> Result<ConversionResult<Option<T>>, ()> {
688        if value.get().is_null_or_undefined() {
689            Ok(ConversionResult::Success(None))
690        } else {
691            Ok(match FromJSValConvertible::from_jsval(cx, value, option)? {
692                ConversionResult::Success(v) => ConversionResult::Success(Some(v)),
693                ConversionResult::Failure(v) => ConversionResult::Failure(v),
694            })
695        }
696    }
697}
698
699impl<T: ToJSValConvertible> ToJSValConvertible for &'_ T {
700    #[inline]
701    fn to_jsval(&self, cx: &mut JSContext, rval: MutableHandleValue) {
702        (**self).to_jsval(cx, rval)
703    }
704}
705
706impl<T: ToJSValConvertible> ToJSValConvertible for Box<T> {
707    #[inline]
708    fn to_jsval(&self, cx: &mut JSContext, rval: MutableHandleValue) {
709        (**self).to_jsval(cx, rval)
710    }
711}
712
713impl<T: ToJSValConvertible> ToJSValConvertible for Rc<T> {
714    #[inline]
715    fn to_jsval(&self, cx: &mut JSContext, rval: MutableHandleValue) {
716        (**self).to_jsval(cx, rval)
717    }
718}
719
720// https://heycam.github.io/webidl/#es-sequence
721impl<T: ToJSValConvertible> ToJSValConvertible for [T] {
722    #[inline]
723    fn to_jsval(&self, cx: &mut JSContext, mut rval: MutableHandleValue) {
724        rooted!(&in(cx) let js_array = unsafe { NewArrayObject1(cx, self.len() as libc::size_t) });
725        assert!(!js_array.handle().is_null());
726
727        rooted!(&in(cx) let mut val = UndefinedValue());
728        for (index, obj) in self.iter().enumerate() {
729            obj.to_jsval(cx, val.handle_mut());
730
731            assert!(unsafe {
732                JS_DefineElement(
733                    cx,
734                    js_array.handle(),
735                    index as u32,
736                    val.handle(),
737                    JSPROP_ENUMERATE as u32,
738                )
739            });
740        }
741
742        rval.set(ObjectValue(js_array.handle().get()));
743    }
744}
745
746// https://heycam.github.io/webidl/#es-sequence
747impl<T: ToJSValConvertible> ToJSValConvertible for Vec<T> {
748    #[inline]
749    fn to_jsval(&self, cx: &mut JSContext, rval: MutableHandleValue) {
750        <[_]>::to_jsval(self, cx, rval)
751    }
752}
753
754impl<C: Clone, T: FromJSValConvertible<Config = C>> FromJSValConvertible for Vec<T> {
755    type Config = C;
756
757    fn from_jsval(
758        cx: &mut JSContext,
759        value: HandleValue,
760        option: C,
761    ) -> Result<ConversionResult<Vec<T>>, ()> {
762        if !value.is_object() {
763            return Ok(ConversionResult::Failure(c"Value is not an object".into()));
764        }
765
766        let mut return_value = vec![];
767        let result = for_of(cx, value, |cx, iterator_element| {
768            let conversion_result = T::from_jsval(cx, iterator_element, option.clone())
769                .map_err(|_| ForOfIterationFailure::JSFailed)?;
770            return_value.push(match conversion_result {
771                ConversionResult::Success(value) => value,
772                ConversionResult::Failure(error) => {
773                    return Err(ForOfIterationFailure::Other(error));
774                }
775            });
776
777            Ok(ControlFlow::Continue(()))
778        });
779
780        match result {
781            Ok(_) => Ok(ConversionResult::Success(return_value)),
782            Err(ForOfIterationFailure::ValueIsNotIterable) => {
783                Ok(ConversionResult::Failure(c"Value is not iterable".into()))
784            }
785            Err(ForOfIterationFailure::JSFailed) => Err(()),
786            Err(ForOfIterationFailure::Other(error)) => {
787                throw_type_error(cx, error.as_ref());
788                Err(())
789            }
790        }
791    }
792}
793
794// https://heycam.github.io/webidl/#es-object
795impl ToJSValConvertible for *mut JSObject {
796    #[inline]
797    fn to_jsval(&self, cx: &mut JSContext, mut rval: MutableHandleValue) {
798        rval.set(ObjectOrNullValue(*self));
799        maybe_wrap_object_or_null_value(cx, rval);
800    }
801}
802
803// https://heycam.github.io/webidl/#es-object
804impl ToJSValConvertible for ptr::NonNull<JSObject> {
805    #[inline]
806    fn to_jsval(&self, cx: &mut JSContext, mut rval: MutableHandleValue) {
807        rval.set(ObjectValue(self.as_ptr()));
808        unsafe { maybe_wrap_object_value(cx, rval) };
809    }
810}
811
812// https://heycam.github.io/webidl/#es-object
813impl ToJSValConvertible for Heap<*mut JSObject> {
814    #[inline]
815    fn to_jsval(&self, cx: &mut JSContext, mut rval: MutableHandleValue) {
816        rval.set(ObjectOrNullValue(self.get()));
817        maybe_wrap_object_or_null_value(cx, rval);
818    }
819}
820
821// https://heycam.github.io/webidl/#es-object
822impl FromJSValConvertible for *mut JSObject {
823    type Config = ();
824
825    #[inline]
826    fn from_jsval(
827        cx: &mut JSContext,
828        value: HandleValue,
829        _option: (),
830    ) -> Result<ConversionResult<*mut JSObject>, ()> {
831        if !value.is_object() {
832            throw_type_error(cx, c"value is not an object");
833            return Err(());
834        }
835
836        unsafe { AssertSameCompartment(cx, value.to_object()) };
837
838        Ok(ConversionResult::Success(value.to_object()))
839    }
840}
841
842impl ToJSValConvertible for *mut JS::Symbol {
843    #[inline]
844    fn to_jsval(&self, _cx: &mut JSContext, mut rval: MutableHandleValue) {
845        unsafe { rval.set(SymbolValue(&**self)) };
846    }
847}
848
849impl FromJSValConvertible for *mut JS::Symbol {
850    type Config = ();
851
852    #[inline]
853    fn from_jsval(
854        cx: &mut JSContext,
855        value: HandleValue,
856        _option: (),
857    ) -> Result<ConversionResult<*mut JS::Symbol>, ()> {
858        if !value.is_symbol() {
859            throw_type_error(cx, c"value is not a symbol");
860            return Err(());
861        }
862
863        Ok(ConversionResult::Success(value.to_symbol()))
864    }
865}
866
867/// A wrapper type over [`crate::jsapi::UTF8Chars`]. This is created to help transferring
868/// a rust string to mozjs. The inner [`crate::jsapi::UTF8Chars`] can be accessed via the
869/// [`std::ops::Deref`] trait.
870pub struct Utf8Chars<'a> {
871    lt_marker: std::marker::PhantomData<&'a ()>,
872    inner: crate::jsapi::UTF8Chars,
873}
874
875impl<'a> std::ops::Deref for Utf8Chars<'a> {
876    type Target = crate::jsapi::UTF8Chars;
877
878    fn deref(&self) -> &Self::Target {
879        &self.inner
880    }
881}
882
883impl<'a> From<&'a str> for Utf8Chars<'a> {
884    #[allow(unsafe_code)]
885    fn from(value: &'a str) -> Self {
886        use std::marker::PhantomData;
887
888        use crate::jsapi::mozilla::{Range, RangedPtr};
889        use crate::jsapi::UTF8Chars;
890
891        let range = value.as_bytes().as_ptr_range();
892        let range_start = range.start as *mut _;
893        let range_end = range.end as *mut _;
894        let start = RangedPtr {
895            _phantom_0: PhantomData,
896            mPtr: range_start,
897            #[cfg(feature = "debugmozjs")]
898            mRangeStart: range_start,
899            #[cfg(feature = "debugmozjs")]
900            mRangeEnd: range_end,
901        };
902        let end = RangedPtr {
903            _phantom_0: PhantomData,
904            mPtr: range_end,
905            #[cfg(feature = "debugmozjs")]
906            mRangeStart: range_start,
907            #[cfg(feature = "debugmozjs")]
908            mRangeEnd: range_end,
909        };
910        let base = Range {
911            _phantom_0: PhantomData,
912            mStart: start,
913            mEnd: end,
914        };
915        let inner = UTF8Chars { _base: base };
916        Self {
917            lt_marker: PhantomData,
918            inner,
919        }
920    }
921}