ul_next/javascript/
value.rs

1use core::fmt;
2use std::ops::Deref;
3
4use super::{JSContext, JSObject, JSString, JSTypedArray, JSTypedArrayType};
5
6/// A trait for converting a type into a [`JSValue`].
7///
8/// Types implementing this trait are [`JSValue`], we are wrapping them in other types
9/// to provide specific functionality.
10pub trait AsJSValue<'a>: Deref<Target = JSValue<'a>> + AsRef<JSValue<'a>> {
11    fn into_value(self) -> JSValue<'a>;
12    fn as_value(&self) -> &JSValue<'a>;
13}
14
15/// An enum identifying the type of a [`JSValue`].
16#[repr(u32)]
17#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
18pub enum JSType {
19    Undefined = ul_sys::JSType_kJSTypeUndefined,
20    Null = ul_sys::JSType_kJSTypeNull,
21    Boolean = ul_sys::JSType_kJSTypeBoolean,
22    Number = ul_sys::JSType_kJSTypeNumber,
23    String = ul_sys::JSType_kJSTypeString,
24    Object = ul_sys::JSType_kJSTypeObject,
25    Symbol = ul_sys::JSType_kJSTypeSymbol,
26}
27
28/// A JavaScript value.
29///
30/// This is the basic type for managing values in JavaScriptCore. It can represent
31/// any JavaScript value, including `undefined`, `null`, booleans, numbers, strings,
32/// symbols, objects, and arrays.
33pub struct JSValue<'a> {
34    pub(crate) internal: ul_sys::JSValueRef,
35    pub(crate) ctx: &'a JSContext,
36}
37
38impl<'a> JSValue<'a> {
39    pub(crate) fn from_raw(ctx: &'a JSContext, value: ul_sys::JSValueRef) -> Self {
40        assert!(!value.is_null());
41
42        Self {
43            internal: value,
44            ctx,
45        }
46    }
47
48    pub(crate) fn copy_from_raw(ctx: &'a JSContext, value: ul_sys::JSValueRef) -> Self {
49        assert!(!value.is_null());
50
51        unsafe {
52            ctx.lib.ultralight().JSValueProtect(ctx.internal, value);
53        }
54
55        Self::from_raw(ctx, value)
56    }
57
58    pub(crate) fn into_raw(self) -> ul_sys::JSValueRef {
59        // add protection so that the `Drop` impl doesn't free while we need it
60        unsafe {
61            self.ctx
62                .lib
63                .ultralight()
64                .JSValueProtect(self.ctx.internal, self.internal);
65        }
66
67        self.internal
68    }
69}
70
71impl<'a> JSValue<'a> {
72    /// Creates a Javascript `undefined` value.
73    pub fn new_undefined(ctx: &'a JSContext) -> Self {
74        let value = unsafe { ctx.lib.ultralight().JSValueMakeUndefined(ctx.internal) };
75
76        Self {
77            internal: value,
78            ctx,
79        }
80    }
81
82    /// Creates a Javascript `null` value.
83    pub fn new_null(ctx: &'a JSContext) -> Self {
84        let value = unsafe { ctx.lib.ultralight().JSValueMakeNull(ctx.internal) };
85
86        Self {
87            internal: value,
88            ctx,
89        }
90    }
91
92    /// Creates a Javascript boolean value.
93    pub fn new_boolean(ctx: &'a JSContext, value: bool) -> Self {
94        let value = unsafe { ctx.lib.ultralight().JSValueMakeBoolean(ctx.internal, value) };
95
96        Self {
97            internal: value,
98            ctx,
99        }
100    }
101
102    /// Creates a Javascript numeric value.
103    pub fn new_number(ctx: &'a JSContext, value: f64) -> Self {
104        let value = unsafe { ctx.lib.ultralight().JSValueMakeNumber(ctx.internal, value) };
105
106        Self {
107            internal: value,
108            ctx,
109        }
110    }
111
112    /// Creates a unique Javascript symbol object.
113    ///
114    /// `description` is a string that describes the symbol.
115    pub fn new_symbol(ctx: &'a JSContext, description: &str) -> Self {
116        let value = JSString::new(ctx.lib.clone(), description);
117
118        let value = unsafe {
119            ctx.lib
120                .ultralight()
121                .JSValueMakeSymbol(ctx.internal, value.internal)
122        };
123
124        Self {
125            internal: value,
126            ctx,
127        }
128    }
129
130    /// Converts a Javascript string object to a Javascript value.
131    pub fn from_jsstring(ctx: &'a JSContext, value: JSString) -> Self {
132        let value = unsafe {
133            ctx.lib
134                .ultralight()
135                .JSValueMakeString(ctx.internal, value.internal)
136        };
137
138        Self {
139            internal: value,
140            ctx,
141        }
142    }
143
144    /// Creates a Javascript string value from a Rust string.
145    ///
146    /// If you have already `JSString` object, use [`JSValue::from_jsstring`] instead.
147    pub fn new_string(ctx: &'a JSContext, value: &str) -> Self {
148        let value = JSString::new(ctx.lib.clone(), value);
149
150        let value = unsafe {
151            ctx.lib
152                .ultralight()
153                .JSValueMakeString(ctx.internal, value.internal)
154        };
155
156        Self {
157            internal: value,
158            ctx,
159        }
160    }
161
162    /// Creates a JavaScript value from a JSON formatted string.
163    ///
164    /// Returns [`None`] if the JSON string is invalid.
165    pub fn new_from_json(ctx: &'a JSContext, value: &str) -> Option<Self> {
166        let value = JSString::new(ctx.lib.clone(), value);
167
168        let value = unsafe {
169            ctx.lib
170                .ultralight()
171                .JSValueMakeFromJSONString(ctx.internal, value.internal)
172        };
173
174        if value.is_null() {
175            None
176        } else {
177            Some(Self {
178                internal: value,
179                ctx,
180            })
181        }
182    }
183}
184
185impl JSValue<'_> {
186    /// Returns a JavaScript value's type.
187    pub fn get_type(&self) -> JSType {
188        let ty = unsafe {
189            self.ctx
190                .lib
191                .ultralight()
192                .JSValueGetType(self.ctx.internal, self.internal)
193        };
194
195        match ty {
196            ul_sys::JSType_kJSTypeUndefined => JSType::Undefined,
197            ul_sys::JSType_kJSTypeNull => JSType::Null,
198            ul_sys::JSType_kJSTypeBoolean => JSType::Boolean,
199            ul_sys::JSType_kJSTypeNumber => JSType::Number,
200            ul_sys::JSType_kJSTypeString => JSType::String,
201            ul_sys::JSType_kJSTypeObject => JSType::Object,
202            ul_sys::JSType_kJSTypeSymbol => JSType::Symbol,
203            _ => panic!("Unknown JSValue type: {}", ty),
204        }
205    }
206
207    /// Returns `true` if the value is `undefined`.
208    pub fn is_undefined(&self) -> bool {
209        unsafe {
210            self.ctx
211                .lib
212                .ultralight()
213                .JSValueIsUndefined(self.ctx.internal, self.internal)
214        }
215    }
216
217    /// Returns `true` if the value is `null`.
218    pub fn is_null(&self) -> bool {
219        unsafe {
220            self.ctx
221                .lib
222                .ultralight()
223                .JSValueIsNull(self.ctx.internal, self.internal)
224        }
225    }
226
227    /// Returns `true` if the value is a JavaScript date object.
228    pub fn is_date(&self) -> bool {
229        unsafe {
230            self.ctx
231                .lib
232                .ultralight()
233                .JSValueIsDate(self.ctx.internal, self.internal)
234        }
235    }
236
237    /// Returns `true` if the value is a JavaScript array.
238    pub fn is_array(&self) -> bool {
239        unsafe {
240            self.ctx
241                .lib
242                .ultralight()
243                .JSValueIsArray(self.ctx.internal, self.internal)
244        }
245    }
246
247    /// Returns `true` if the value's type is the symbol type
248    pub fn is_symbol(&self) -> bool {
249        unsafe {
250            self.ctx
251                .lib
252                .ultralight()
253                .JSValueIsSymbol(self.ctx.internal, self.internal)
254        }
255    }
256
257    /// Returns `true` if the value is an object.
258    pub fn is_object(&self) -> bool {
259        unsafe {
260            self.ctx
261                .lib
262                .ultralight()
263                .JSValueIsObject(self.ctx.internal, self.internal)
264        }
265    }
266
267    /// Returns `true` if the value is a string.
268    pub fn is_string(&self) -> bool {
269        unsafe {
270            self.ctx
271                .lib
272                .ultralight()
273                .JSValueIsString(self.ctx.internal, self.internal)
274        }
275    }
276
277    /// Returns `true` if the value is a number.
278    pub fn is_number(&self) -> bool {
279        unsafe {
280            self.ctx
281                .lib
282                .ultralight()
283                .JSValueIsNumber(self.ctx.internal, self.internal)
284        }
285    }
286
287    /// Returns `true` if the value is a boolean.
288    pub fn is_boolean(&self) -> bool {
289        unsafe {
290            self.ctx
291                .lib
292                .ultralight()
293                .JSValueIsBoolean(self.ctx.internal, self.internal)
294        }
295    }
296
297    /// Returns `true` if the value is a Typed Array.
298    pub fn is_typed_array(&self) -> bool {
299        // Note: we are creating the object `JSTypedArray` here to check if the value is a typed
300        // array, i.e. it shouldn't be used to call any other `JSTypedArray` methods.
301        let typed_array = JSTypedArray {
302            value: Self {
303                internal: self.internal,
304                ctx: self.ctx,
305            },
306        };
307
308        match typed_array.ty().ok() {
309            None | Some(JSTypedArrayType::None) => false,
310            Some(_) => true,
311        }
312    }
313}
314
315impl<'a> JSValue<'a> {
316    /// Converts a JavaScript value to object.
317    ///
318    /// Returns an [`Err`] if an exception is thrown.
319    pub fn as_object(&self) -> Result<JSObject<'a>, JSValue<'a>> {
320        let mut exception = std::ptr::null();
321
322        let result = unsafe {
323            self.ctx.lib.ultralight().JSValueToObject(
324                self.ctx.internal,
325                self.internal,
326                &mut exception,
327            )
328        };
329
330        if !exception.is_null() {
331            Err(JSValue::from_raw(self.ctx, exception))
332        } else if result.is_null() {
333            Err(JSValue::new_string(
334                self.ctx,
335                "Failed to convert value to object",
336            ))
337        } else {
338            Ok(JSObject {
339                value: JSValue::from_raw(self.ctx, result),
340            })
341        }
342    }
343
344    /// Converts a JavaScript value to string.
345    ///
346    /// Returns an [`Err`] if an exception is thrown.
347    pub fn as_string(&self) -> Result<JSString, JSValue<'a>> {
348        let mut exception = std::ptr::null();
349
350        let result = unsafe {
351            self.ctx.lib.ultralight().JSValueToStringCopy(
352                self.ctx.internal,
353                self.internal,
354                &mut exception,
355            )
356        };
357
358        if !exception.is_null() {
359            Err(JSValue::from_raw(self.ctx, exception))
360        } else if result.is_null() {
361            Err(JSValue::new_string(
362                self.ctx,
363                "Failed to convert value to string",
364            ))
365        } else {
366            Ok(JSString::copy_from_raw(self.ctx.lib.clone(), result))
367        }
368    }
369
370    /// Converts a JavaScript value to number.
371    ///
372    /// Returns an [`Err`] if an exception is thrown.
373    pub fn as_number(&self) -> Result<f64, JSValue<'a>> {
374        let mut exception = std::ptr::null();
375
376        let result = unsafe {
377            self.ctx.lib.ultralight().JSValueToNumber(
378                self.ctx.internal,
379                self.internal,
380                &mut exception,
381            )
382        };
383
384        if !exception.is_null() {
385            Err(JSValue::from_raw(self.ctx, exception))
386        } else {
387            Ok(result)
388        }
389    }
390
391    /// Converts a JavaScript value to boolean.
392    pub fn as_boolean(&self) -> bool {
393        unsafe {
394            self.ctx
395                .lib
396                .ultralight()
397                .JSValueToBoolean(self.ctx.internal, self.internal)
398        }
399    }
400
401    /// Converts a JavaScript value to a typed array.
402    ///
403    /// Returns an [`Err`] if the value is not a typed array, or if an exception is thrown.
404    pub fn as_typed_array(&self) -> Result<JSTypedArray<'a>, JSValue<'a>> {
405        if self.is_typed_array() {
406            let object = self.as_object()?;
407
408            Ok(JSTypedArray {
409                value: object.value,
410            })
411        } else {
412            Err(JSValue::new_string(self.ctx, "Value is not a typed array"))
413        }
414    }
415
416    /// Converts a JavaScript value to JSON serialized representation of a JS value.
417    ///
418    /// Returns an [`Err`] if an exception is thrown.
419    pub fn to_json_string(&self) -> Result<JSString, JSValue<'a>> {
420        let mut exception = std::ptr::null();
421
422        let result = unsafe {
423            self.ctx.lib.ultralight().JSValueCreateJSONString(
424                self.ctx.internal,
425                self.internal,
426                0,
427                &mut exception,
428            )
429        };
430
431        if !exception.is_null() {
432            Err(JSValue::from_raw(self.ctx, exception))
433        } else if result.is_null() {
434            Err(JSValue::new_string(
435                self.ctx,
436                "Failed to convert value to JSON string",
437            ))
438        } else {
439            Ok(JSString::from_raw(self.ctx.lib.clone(), result))
440        }
441    }
442}
443
444impl Clone for JSValue<'_> {
445    fn clone(&self) -> Self {
446        unsafe {
447            self.ctx
448                .lib
449                .ultralight()
450                .JSValueProtect(self.ctx.internal, self.internal)
451        };
452
453        Self {
454            internal: self.internal,
455            ctx: self.ctx,
456        }
457    }
458}
459
460impl fmt::Debug for JSValue<'_> {
461    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
462        f.debug_struct("JSValue")
463            .field("type", &self.get_type())
464            .field("repr", &self.to_json_string())
465            .finish()
466    }
467}
468
469impl Drop for JSValue<'_> {
470    fn drop(&mut self) {
471        unsafe {
472            self.ctx
473                .lib
474                .ultralight()
475                .JSValueUnprotect(self.ctx.internal, self.internal);
476        }
477    }
478}