1use core::fmt;
2use std::{
3 ops::Deref,
4 sync::{Arc, OnceLock},
5};
6
7use crate::Library;
8
9use super::{AsJSValue, JSContext, JSString, JSValue};
10
11static LIBRARY: OnceLock<Arc<Library>> = OnceLock::new();
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
19pub struct JSPropertyAttributes {
20 pub read_only: bool,
22 pub dont_enum: bool,
25 pub dont_delete: bool,
27}
28
29impl JSPropertyAttributes {
30 pub fn new() -> Self {
32 Self::default()
33 }
34
35 pub fn read_only(mut self, read_only: bool) -> Self {
37 self.read_only = read_only;
38 self
39 }
40
41 pub fn dont_enum(mut self, dont_enum: bool) -> Self {
44 self.dont_enum = dont_enum;
45 self
46 }
47
48 pub fn dont_delete(mut self, dont_delete: bool) -> Self {
50 self.dont_delete = dont_delete;
51 self
52 }
53
54 pub(crate) fn to_raw(self) -> u32 {
55 let mut raw = 0;
56
57 if self.read_only {
58 raw |= ul_sys::kJSPropertyAttributeReadOnly;
59 }
60
61 if self.dont_enum {
62 raw |= ul_sys::kJSPropertyAttributeDontEnum;
63 }
64
65 if self.dont_delete {
66 raw |= ul_sys::kJSPropertyAttributeDontDelete;
67 }
68
69 raw
70 }
71}
72
73#[derive(Clone, Debug)]
75pub struct JSObject<'a> {
76 pub(crate) value: JSValue<'a>,
77}
78
79impl<'a> JSObject<'a> {
80 pub(crate) fn copy_from_raw(ctx: &'a JSContext, obj: ul_sys::JSObjectRef) -> Self {
81 assert!(!obj.is_null());
82
83 unsafe { ctx.lib.ultralight().JSValueProtect(ctx.internal, obj) };
85
86 Self {
87 value: JSValue::from_raw(ctx, obj),
88 }
89 }
90
91 pub fn new(ctx: &'a JSContext) -> Self {
93 let obj = unsafe {
94 ctx.lib.ultralight().JSObjectMake(
95 ctx.internal,
96 std::ptr::null_mut(),
97 std::ptr::null_mut(),
98 )
99 };
100
101 Self {
102 value: JSValue::from_raw(ctx, obj),
103 }
104 }
105
106 pub fn new_function_with_callback<F>(ctx: &'a JSContext, callback: F) -> Self
112 where
113 for<'c> F:
114 FnMut(&'c JSContext, &JSObject<'c>, &[JSValue<'c>]) -> Result<JSValue<'c>, JSValue<'c>>,
115 {
116 LIBRARY.get_or_init(|| ctx.lib.clone());
117
118 unsafe extern "C" fn finalize<Env>(function: ul_sys::JSObjectRef)
119 where
120 for<'c> Env: FnMut(
121 &'c JSContext,
122 &JSObject<'c>,
123 &[JSValue<'c>],
124 ) -> Result<JSValue<'c>, JSValue<'c>>,
125 {
126 let _guard = scopeguard::guard_on_unwind((), |()| {
127 ::std::process::abort();
128 });
129
130 let lib = ffi_unwrap!(LIBRARY.get(), "library undefined ptr",);
131
132 let private_data = lib.ultralight().JSObjectGetPrivate(function) as *mut Box<Env>;
133
134 let _ = Box::from_raw(private_data);
135 }
136
137 unsafe extern "C" fn trampoline<Env>(
138 ctx: ul_sys::JSContextRef,
139 function: ul_sys::JSObjectRef,
140 this_object: ul_sys::JSObjectRef,
141 argument_count: usize,
142 arguments: *const ul_sys::JSValueRef,
143 exception: *mut ul_sys::JSValueRef,
144 ) -> ul_sys::JSValueRef
145 where
146 for<'c> Env: FnMut(
147 &'c JSContext,
148 &JSObject<'c>,
149 &[JSValue<'c>],
150 ) -> Result<JSValue<'c>, JSValue<'c>>,
151 {
152 let _guard = scopeguard::guard_on_unwind((), |()| {
153 ::std::process::abort();
154 });
155
156 let lib = ffi_unwrap!(LIBRARY.get(), "library undefined ptr",);
157
158 let private_data = lib.ultralight().JSObjectGetPrivate(function) as *mut Box<Env>;
159 let callback: &mut Box<Env> = ffi_unwrap!(private_data.as_mut(), "null ptr",);
160
161 let ctx = JSContext::copy_from_raw(lib.clone(), ctx);
162 let this = JSObject::copy_from_raw(&ctx, this_object);
163 let args = std::slice::from_raw_parts(arguments, argument_count)
164 .iter()
165 .map(|v| JSValue::copy_from_raw(&ctx, *v))
166 .collect::<Vec<_>>();
167
168 let ret = callback(&ctx, &this, &args);
169 match ret {
170 Ok(value) => value.into_raw(),
171 Err(value) => {
172 if !exception.is_null() {
173 *exception = value.into_raw();
174 }
175 std::ptr::null_mut()
176 }
177 }
178 }
179
180 let c_callback: ul_sys::JSObjectCallAsFunctionCallback = Some(trampoline::<F>);
181 let callback_data: *mut Box<F> = Box::into_raw(Box::new(Box::new(callback)));
182
183 let class_def = ul_sys::JSClassDefinition {
184 finalize: Some(finalize::<F>),
185 callAsFunction: c_callback,
186 ..super::class::EMPTY_CLASS_DEF
187 };
188
189 let obj = unsafe {
190 let class = ctx.lib.ultralight().JSClassCreate(&class_def);
191
192 let obj = ctx
193 .lib
194 .ultralight()
195 .JSObjectMake(ctx.internal, class, callback_data as _);
196
197 ctx.lib.ultralight().JSClassRelease(class);
198
199 obj
200 };
201
202 Self {
203 value: JSValue::from_raw(ctx, obj),
204 }
205 }
206
207 pub fn new_function(
215 ctx: &'a JSContext,
216 name: Option<&str>,
217 param_names: &[&str],
218 body: &str,
219 source_url: Option<&str>,
220 starting_line_number: i32,
221 ) -> Result<Self, JSValue<'a>> {
222 let mut exception = std::ptr::null();
223 let name = name.map(|n| JSString::new(ctx.lib.clone(), n));
224
225 let param_names: Vec<_> = param_names
226 .iter()
227 .map(|n| JSString::new(ctx.lib.clone(), n))
228 .collect();
229 let params_ptrs: Vec<_> = param_names.iter().map(|n| n.internal).collect();
230
231 let body = JSString::new(ctx.lib.clone(), body);
232 let source_url = source_url.map(|s| JSString::new(ctx.lib.clone(), s));
233
234 let obj = unsafe {
235 ctx.lib.ultralight().JSObjectMakeFunction(
236 ctx.internal,
237 name.map_or(std::ptr::null_mut(), |n| n.internal),
238 param_names.len() as _,
239 if param_names.is_empty() {
240 std::ptr::null()
241 } else {
242 params_ptrs.as_ptr()
243 },
244 body.internal,
245 source_url.map_or(std::ptr::null_mut(), |s| s.internal),
246 starting_line_number,
247 &mut exception,
248 )
249 };
250
251 if !exception.is_null() {
252 Err(JSValue::from_raw(ctx, exception))
253 } else if obj.is_null() {
254 Err(JSValue::new_string(ctx, "Failed to create function"))
255 } else {
256 Ok(Self {
257 value: JSValue::from_raw(ctx, obj),
258 })
259 }
260 }
261
262 pub fn new_array(ctx: &'a JSContext, items: &[JSValue]) -> Result<Self, JSValue<'a>> {
264 let items_ptrs: Vec<_> = items.iter().map(|v| v.internal).collect();
265
266 let mut exception = std::ptr::null();
267
268 let result = unsafe {
269 ctx.lib.ultralight().JSObjectMakeArray(
270 ctx.internal,
271 items.len() as _,
272 items_ptrs.as_ptr(),
273 &mut exception,
274 )
275 };
276
277 if !exception.is_null() {
278 Err(JSValue::from_raw(ctx, exception))
279 } else if result.is_null() {
280 Err(JSValue::new_string(ctx, "Failed to create array"))
281 } else {
282 Ok(Self {
283 value: JSValue::from_raw(ctx, result),
284 })
285 }
286 }
287
288 pub fn is_function(&self) -> bool {
290 unsafe {
291 self.value
292 .ctx
293 .lib
294 .ultralight()
295 .JSObjectIsFunction(self.value.ctx.internal, self.value.internal as _)
296 }
297 }
298
299 pub fn is_constructor(&self) -> bool {
301 unsafe {
302 self.value
303 .ctx
304 .lib
305 .ultralight()
306 .JSObjectIsConstructor(self.value.ctx.internal, self.value.internal as _)
307 }
308 }
309
310 pub fn call_as_function(
315 &self,
316 this: Option<&JSObject>,
317 args: &[JSValue],
318 ) -> Result<JSValue, JSValue> {
319 let mut exception = std::ptr::null();
320
321 let args: Vec<_> = args.iter().map(|v| v.internal).collect();
322
323 let result_raw = unsafe {
324 self.value.ctx.lib.ultralight().JSObjectCallAsFunction(
325 self.value.ctx.internal,
326 self.value.internal as _,
327 this.map_or(std::ptr::null_mut(), |v| v.internal as _),
328 args.len(),
329 args.as_ptr(),
330 &mut exception,
331 )
332 };
333
334 if !exception.is_null() {
335 Err(JSValue::from_raw(self.value.ctx, exception))
336 } else if result_raw.is_null() {
337 Err(JSValue::new_string(
338 self.value.ctx,
339 "Failed to call function",
340 ))
341 } else {
342 Ok(JSValue::from_raw(self.value.ctx, result_raw))
343 }
344 }
345
346 pub fn call_as_constructor(&self, args: &[JSValue]) -> Result<JSObject, JSValue> {
351 let mut exception = std::ptr::null();
352
353 let args: Vec<_> = args.iter().map(|v| v.internal).collect();
354
355 let result_raw = unsafe {
356 self.value.ctx.lib.ultralight().JSObjectCallAsConstructor(
357 self.value.ctx.internal,
358 self.value.internal as _,
359 args.len(),
360 args.as_ptr(),
361 &mut exception,
362 )
363 };
364
365 if !exception.is_null() {
366 Err(JSValue::from_raw(self.value.ctx, exception))
367 } else if result_raw.is_null() {
368 Err(JSValue::new_string(
369 self.value.ctx,
370 "Failed to call constructor",
371 ))
372 } else {
373 Ok(JSObject::copy_from_raw(self.value.ctx, result_raw))
374 }
375 }
376}
377
378impl JSObject<'_> {
379 pub fn get_property(&self, name: &str) -> Result<JSValue, JSValue> {
384 let name = JSString::new(self.ctx.lib.clone(), name);
385 let mut exception = std::ptr::null();
386
387 let result_raw = unsafe {
388 self.ctx.lib.ultralight().JSObjectGetProperty(
389 self.ctx.internal,
390 self.internal as _,
391 name.internal,
392 &mut exception,
393 )
394 };
395
396 if !exception.is_null() {
397 Err(JSValue::from_raw(self.ctx, exception))
398 } else if result_raw.is_null() {
399 Err(JSValue::new_string(self.ctx, "Failed to get property"))
400 } else {
401 Ok(JSValue::from_raw(self.ctx, result_raw))
402 }
403 }
404
405 pub fn get_property_at_index(&self, index: u32) -> Result<JSValue, JSValue> {
414 let mut exception = std::ptr::null();
415
416 let result_raw = unsafe {
417 self.ctx.lib.ultralight().JSObjectGetPropertyAtIndex(
418 self.ctx.internal,
419 self.internal as _,
420 index,
421 &mut exception,
422 )
423 };
424
425 if !exception.is_null() {
426 Err(JSValue::from_raw(self.ctx, exception))
427 } else if result_raw.is_null() {
428 Err(JSValue::new_string(self.ctx, "Failed to get property"))
429 } else {
430 Ok(JSValue::from_raw(self.ctx, result_raw))
431 }
432 }
433
434 pub fn set_property(
438 &self,
439 name: &str,
440 value: &JSValue,
441 attributes: JSPropertyAttributes,
442 ) -> Result<(), JSValue> {
443 let name = JSString::new(self.ctx.lib.clone(), name);
444 let mut exception = std::ptr::null();
445
446 unsafe {
447 self.ctx.lib.ultralight().JSObjectSetProperty(
448 self.ctx.internal,
449 self.internal as _,
450 name.internal,
451 value.internal,
452 attributes.to_raw(),
453 &mut exception,
454 );
455 }
456
457 if !exception.is_null() {
458 Err(JSValue::from_raw(self.ctx, exception))
459 } else {
460 Ok(())
461 }
462 }
463
464 pub fn set_property_at_index(&self, index: u32, value: &JSValue) -> Result<(), JSValue> {
472 let mut exception = std::ptr::null();
473
474 unsafe {
475 self.ctx.lib.ultralight().JSObjectSetPropertyAtIndex(
476 self.ctx.internal,
477 self.internal as _,
478 index,
479 value.internal,
480 &mut exception,
481 );
482 }
483
484 if !exception.is_null() {
485 Err(JSValue::from_raw(self.ctx, exception))
486 } else {
487 Ok(())
488 }
489 }
490
491 pub fn get_property_names(&self) -> JSPropertyNameArray {
493 let names = unsafe {
494 self.ctx
495 .lib
496 .ultralight()
497 .JSObjectCopyPropertyNames(self.ctx.internal, self.internal as _)
498 };
499
500 JSPropertyNameArray::from_raw(self.ctx, names)
501 }
502
503 pub fn has_property(&self, name: &str) -> bool {
505 let name = JSString::new(self.ctx.lib.clone(), name);
506
507 unsafe {
508 self.ctx.lib.ultralight().JSObjectHasProperty(
509 self.ctx.internal,
510 self.internal as _,
511 name.internal,
512 )
513 }
514 }
515
516 pub fn delete_property(&self, name: &str) -> Result<bool, JSValue> {
521 let name = JSString::new(self.ctx.lib.clone(), name);
522 let mut exception = std::ptr::null();
523
524 let result = unsafe {
525 self.ctx.lib.ultralight().JSObjectDeleteProperty(
526 self.ctx.internal,
527 self.internal as _,
528 name.internal,
529 &mut exception,
530 )
531 };
532
533 if !exception.is_null() {
534 Err(JSValue::from_raw(self.ctx, exception))
535 } else {
536 Ok(result)
537 }
538 }
539
540 pub fn get_property_for_key(&self, key: &JSValue) -> Result<JSValue, JSValue> {
547 let mut exception = std::ptr::null();
548
549 let result_raw = unsafe {
550 self.ctx.lib.ultralight().JSObjectGetPropertyForKey(
551 self.ctx.internal,
552 self.internal as _,
553 key.internal,
554 &mut exception,
555 )
556 };
557
558 if !exception.is_null() {
559 Err(JSValue::from_raw(self.ctx, exception))
560 } else if result_raw.is_null() {
561 Err(JSValue::new_string(self.ctx, "Failed to get property"))
562 } else {
563 Ok(JSValue::from_raw(self.ctx, result_raw))
564 }
565 }
566
567 pub fn set_property_for_key(
573 &self,
574 key: &JSValue,
575 value: &JSValue,
576 attributes: JSPropertyAttributes,
577 ) -> Result<(), JSValue> {
578 let mut exception = std::ptr::null();
579
580 unsafe {
581 self.ctx.lib.ultralight().JSObjectSetPropertyForKey(
582 self.ctx.internal,
583 self.internal as _,
584 key.internal,
585 value.internal,
586 attributes.to_raw(),
587 &mut exception,
588 );
589 }
590
591 if !exception.is_null() {
592 Err(JSValue::from_raw(self.ctx, exception))
593 } else {
594 Ok(())
595 }
596 }
597
598 pub fn has_property_for_key(&self, key: &JSValue) -> Result<bool, JSValue> {
602 let mut exception = std::ptr::null();
603
604 let result = unsafe {
605 self.ctx.lib.ultralight().JSObjectHasPropertyForKey(
606 self.ctx.internal,
607 self.internal as _,
608 key.internal,
609 &mut exception,
610 )
611 };
612
613 if !exception.is_null() {
614 Err(JSValue::from_raw(self.ctx, exception))
615 } else {
616 Ok(result)
617 }
618 }
619
620 pub fn delete_property_for_key(&self, key: &JSValue) -> Result<bool, JSValue> {
627 let mut exception = std::ptr::null();
628
629 let result = unsafe {
630 self.ctx.lib.ultralight().JSObjectDeletePropertyForKey(
631 self.ctx.internal,
632 self.internal as _,
633 key.internal,
634 &mut exception,
635 )
636 };
637
638 if !exception.is_null() {
639 Err(JSValue::from_raw(self.ctx, exception))
640 } else {
641 Ok(result)
642 }
643 }
644}
645
646impl<'a> AsRef<JSValue<'a>> for JSObject<'a> {
647 fn as_ref(&self) -> &JSValue<'a> {
648 &self.value
649 }
650}
651
652impl<'a> Deref for JSObject<'a> {
653 type Target = JSValue<'a>;
654
655 fn deref(&self) -> &Self::Target {
656 &self.value
657 }
658}
659
660impl<'a> AsJSValue<'a> for JSObject<'a> {
661 fn into_value(self) -> JSValue<'a> {
662 self.value
663 }
664
665 fn as_value(&self) -> &JSValue<'a> {
666 &self.value
667 }
668}
669
670pub struct JSPropertyNameArray<'a> {
674 internal: ul_sys::JSPropertyNameArrayRef,
675 ctx: &'a JSContext,
676}
677
678impl<'a> JSPropertyNameArray<'a> {
679 pub(crate) fn from_raw(ctx: &'a JSContext, array: ul_sys::JSPropertyNameArrayRef) -> Self {
680 assert!(!array.is_null());
681
682 Self {
683 internal: array,
684 ctx,
685 }
686 }
687
688 pub fn is_empty(&self) -> bool {
690 self.len() == 0
691 }
692
693 pub fn len(&self) -> usize {
695 unsafe {
696 self.ctx
697 .lib
698 .ultralight()
699 .JSPropertyNameArrayGetCount(self.internal)
700 }
701 }
702
703 pub fn get(&self, index: usize) -> Option<JSString> {
705 let name = unsafe {
706 self.ctx
707 .lib
708 .ultralight()
709 .JSPropertyNameArrayGetNameAtIndex(self.internal, index)
710 };
711
712 if name.is_null() {
713 None
714 } else {
715 Some(JSString::copy_from_raw(self.ctx.lib.clone(), name))
716 }
717 }
718
719 pub fn into_vec(self) -> Vec<String> {
721 self.into()
722 }
723}
724
725impl From<JSPropertyNameArray<'_>> for Vec<String> {
726 fn from(array: JSPropertyNameArray) -> Self {
727 let mut names = Vec::with_capacity(array.len());
728
729 for i in 0..array.len() {
730 let name = array
731 .get(i)
732 .expect("Array should still have elements")
733 .to_string();
734 names.push(name);
735 }
736
737 names
738 }
739}
740
741impl Clone for JSPropertyNameArray<'_> {
742 fn clone(&self) -> Self {
743 let array = unsafe {
744 self.ctx
745 .lib
746 .ultralight()
747 .JSPropertyNameArrayRetain(self.internal)
748 };
749
750 Self {
751 internal: array,
752 ctx: self.ctx,
753 }
754 }
755}
756
757impl fmt::Debug for JSPropertyNameArray<'_> {
758 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
759 self.clone().into_vec().fmt(f)
760 }
761}
762
763impl Drop for JSPropertyNameArray<'_> {
764 fn drop(&mut self) {
765 unsafe {
766 self.ctx
767 .lib
768 .ultralight()
769 .JSPropertyNameArrayRelease(self.internal);
770 }
771 }
772}