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