rong_core 0.3.0

Core runtime types for RongJS
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
use self::thrown_store::{ThrownValueHandle, ThrownValueStore};
use crate::{
    ClassSetup, FromJSValue, HostError, JSClass, JSObject, JSObjectOps, JSResult, JSRuntimeImpl,
    JSTypeOf, JSValue, JSValueImpl, Promise, RongJSError,
    source::{Source, SourceKind},
};
use crate::{JSRuntime, JSValueMapper};
use std::any::TypeId;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::{Rc, Weak};
use std::sync::{LazyLock, RwLock};

pub(crate) mod thrown_store;

/// Describes how a JavaScript engine registered promise handlers.
///
/// `core` owns the polling state machine and microtask draining. Engines only
/// report which registration path they performed so the shared layer can apply
/// the right follow-up behavior.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum PromiseHandlerRegistration {
    /// The engine did not install any native handlers. `core` must fall back to
    /// `promise.then(onFulfilled, onRejected)`.
    #[default]
    JavaScriptOnly,
    /// The engine installed native handlers and no JavaScript fallback is needed.
    NativeOnly,
    /// The engine installed native handlers, but `core` should install a
    /// JavaScript `.then()` backup if the promise is still pending after the
    /// first microtask drain.
    NativeWithJavaScriptFallbackIfPending,
}

/// JSContextImpl represents a JavaScript context
///
/// # Safety
/// The implementation must ensure that:
/// 1. Value type implements Drop to properly clean up resources
/// 2. Context type implements Drop if it holds any resources that need cleanup
pub trait JSContextImpl {
    /// The JavaScript engine's native context type.
    ///
    /// This represents the raw, engine-specific context handle that is used internally
    type RawContext;

    /// The runtime type associated with this context.
    ///
    /// This specifies the JavaScript runtime implementation that this context belongs to.
    /// The runtime must implement JSRuntimeImpl and have its Context type set to Self.
    type Runtime: JSRuntimeImpl<Context = Self>;

    /// The JavaScript value type associated with this context.
    ///
    /// This specifies the type used to represent JavaScript values within this context.
    /// The value type must implement JSValueImpl and have its Context type set to Self.
    type Value: JSValueImpl<Context = Self>;

    /// Creates a new JavaScript context
    fn new(runtime: &Self::Runtime) -> Self;

    /// Converts the context to its FFI representation
    fn as_raw(&self) -> &Self::RawContext;

    /// Returns a unique identifier for the context that can be used as a key in CTX_OPAQUE
    ///
    /// This identifier must be:
    /// - Unique per context instance
    /// - Stable for the lifetime of the context
    /// - Suitable for use as a HashMap key
    ///
    /// # Returns
    /// A usize value that uniquely identifies this context instance
    fn context_id(ctx: &Self::RawContext) -> usize;

    /// the implementation need to make sure it has the ownship, like as new method
    /// generally, it should increase referen count of FFI Context
    fn from_borrowed_raw(ctx: Self::RawContext) -> Self;

    /// Evaluate JavaScript code
    fn eval(&self, source: Source) -> Self::Value;

    /// Get global object
    fn global(&self) -> Self::Value;

    /// Register class for rust type
    fn register_class<JC>(&self) -> Self::Value
    where
        JC: JSClass<Self::Value>;

    /// Calls a JavaScript function with the specified `this` value and arguments.
    ///
    /// # Arguments
    ///
    /// * `function` - The JavaScript function to call
    /// * `this` - Optional `this` value to use when calling the function
    /// * `argv` - Slice of arguments to pass to the function
    ///
    /// # Returns
    ///
    /// Returns the result of the function call as a JavaScript value
    fn call(&self, function: &Self::Value, this: Self::Value, argv: &[Self::Value]) -> Self::Value;

    /// Creates a new JavaScript Promise and returns a tuple containing:
    /// - The Promise object
    /// - The resolve function to fulfill the promise
    /// - The reject function to reject the promise
    fn promise(&self) -> (Self::Value, Self::Value, Self::Value);

    /// Registers resolve/reject handlers on a promise at the engine level.
    ///
    /// Engines can override this to use native APIs. The shared `core` future
    /// machinery will later drain microtasks and apply any declared JS fallback,
    /// so implementations should focus on registration and avoid embedding shared
    /// scheduling policy here.
    fn register_promise_handlers(
        &self,
        _promise: &Self::Value,
        _on_fulfilled: &Self::Value,
        _on_rejected: &Self::Value,
    ) -> PromiseHandlerRegistration {
        PromiseHandlerRegistration::JavaScriptOnly
    }

    /// Compiles JavaScript source code into bytecode format
    ///
    /// # Arguments
    /// * `source` - The JavaScript source code to compile
    ///
    /// # Returns
    /// * `Ok(Vec<u8>)` - The compiled bytecode as bytes if compilation succeeds
    /// * `Err(RongJSError)` - If compilation fails with one of these errors:
    ///   - `HostError::new(E_COMPILE, ...)`: General compilation error
    ///   - `HostError::new(E_NOT_SUPPORTED, ...).with_data(err_data!({ feature: "bytecode" }))`:
    ///     Bytecode compilation not supported by runtime
    fn compile_to_bytecode(&self, source: Source) -> Result<Vec<u8>, RongJSError>;

    /// Executes previously compiled bytecode
    ///
    /// # Arguments
    /// * `bytes` - The bytecode bytes to execute
    ///
    /// # Returns
    /// The result of executing the bytecode as a JavaScript value
    fn run_bytecode(&self, bytes: &[u8]) -> Self::Value;
}

pub trait JSRawContext {
    type RawContext;
}

pub struct JSContext<C: JSContextImpl> {
    rc: Rc<JSContextInner<C>>,
}

struct JSContextInner<C: JSContextImpl> {
    inner: C,
    runtime: JSRuntime<C::Runtime>,
    rong: C::Value,
    services: ContextServiceContainer,
}

/// A trait for context-scoped services that can be attached to JSContext.
///
/// This is similar to JSRuntimeService but scoped to a single JSContext instance.
/// Implementors can use on_shutdown to release resources that should be cleaned
/// up when the owning JSContext is dropped.
pub trait JSContextService: 'static {
    /// Called when the JSContext that owns this service is being shutdown.
    fn on_shutdown(&self) {}
}

/// A container for context services with proper lifecycle management.
#[derive(Clone)]
struct ContextServiceContainer {
    services: Rc<RefCell<HashMap<TypeId, Box<dyn JSContextService>>>>,
}

struct ContextState<T: 'static>(T);

impl<T: 'static> JSContextService for ContextState<T> {}

impl ContextServiceContainer {
    fn new() -> Self {
        Self {
            services: Rc::new(RefCell::new(HashMap::new())),
        }
    }

    fn register<T: JSContextService>(&self, service: T) {
        let mut services = self.services.borrow_mut();
        services.insert(TypeId::of::<T>(), Box::new(service));
    }

    fn get<T: JSContextService>(&self) -> Option<&T> {
        // SAFETY: This is safe because:
        // 1. We only insert services through register<T>
        // 2. TypeId is unique for each type
        // 3. The service is never removed until container is shut down
        // 4. The RefCell ensures we don't have multiple mutable borrows
        unsafe {
            let services = self.services.borrow();
            services
                .get(&TypeId::of::<T>())
                .map(|svc| &*(svc.as_ref() as *const dyn JSContextService as *const T))
        }
    }

    fn register_state<T: 'static>(&self, value: T) {
        let mut services = self.services.borrow_mut();
        services.insert(TypeId::of::<T>(), Box::new(ContextState(value)));
    }

    fn get_state<T: 'static>(&self) -> Option<&T> {
        // SAFETY: We store `ContextState<T>` under `TypeId::of::<T>()` in `register_state`.
        unsafe {
            let services = self.services.borrow();
            services
                .get(&TypeId::of::<T>())
                .map(|svc| {
                    &*(svc.as_ref() as *const dyn JSContextService as *const ContextState<T>)
                })
                .map(|state| &state.0)
        }
    }

    fn shutdown(&self) {
        let mut services = self.services.borrow_mut();
        for (_, svc) in services.drain() {
            svc.on_shutdown();
        }
    }
}

impl<C: JSContextImpl> AsRef<C> for JSContext<C> {
    fn as_ref(&self) -> &C {
        &self.rc.inner
    }
}

impl<C: JSContextImpl> JSContext<C> {
    /// Creates a new JavaScript context.
    ///
    /// This function:
    /// 1. Creates a JSContext instance with proper internal structure
    /// 2. Stores the context address in an opaque data structure for FFI callbacks
    /// 3. Sets up weak references to prevent memory leaks
    ///
    /// The context can be safely shared between callbacks and async tasks.
    ///
    /// # Safety
    /// - The context must be dropped on the same thread it was created on
    /// - The runtime must outlive the context
    ///
    /// # Example
    /// ```rust,no_run
    /// use rong_core::prelude::*;
    ///
    /// fn demo<E: JSEngine + 'static>() {
    ///     let runtime = E::runtime();
    ///     let _ctx = runtime.context();
    /// }
    /// ```
    pub(crate) fn new(runtime: &JSRuntime<C::Runtime>) -> Self
    where
        C::Value: JSObjectOps,
    {
        let raw_ctx = C::new(&runtime.inner);
        let rong = C::Value::new_object(&raw_ctx);

        let inner = JSContextInner {
            inner: raw_ctx,
            runtime: runtime.clone(),
            rong,
            services: ContextServiceContainer::new(),
        };

        let ctx = JSContext { rc: Rc::new(inner) };
        let weak = Rc::downgrade(&ctx.rc);

        // save stale address to opaque
        let opaque = ContextOpaque::<C::Value>::new(weak);
        let raw_ctx = ctx.rc.inner.as_raw();
        let key = C::context_id(raw_ctx);
        CTX_OPAQUE
            .write()
            .unwrap()
            .insert(key, Box::into_raw(opaque) as usize);

        ctx
    }

    /// Returns the host namespace object injected into the JavaScript global scope.
    pub fn host_namespace(&self) -> JSObject<C::Value> {
        let value = JSValue::from_raw(self, self.rc.as_ref().rong.clone());
        value.into()
    }

    /// Creates a JSContext from an FFI context pointer.
    ///
    /// This is used in callback scenarios where the JS engine provides a context pointer.
    /// From the JS engine's perspective, contexts created from the mainline and from
    /// callbacks are equivalent since they operate within the same execution context.
    ///
    /// # Safety
    /// - The provided FFI context must be valid and properly aligned
    /// - The caller must ensure the context pointer remains valid for the duration of use
    /// - This should only be called with context pointers obtained from the JS engine
    /// - The returned reference must not outlive the original context
    ///
    /// # Example
    /// ```ignore
    /// // Pseudo-code: this is only usable with a real engine-provided raw context pointer.
    /// unsafe {
    ///     let ctx = JSContext::from_borrowed_raw_ptr(ffi_ctx);
    ///     // Use ctx for the duration of the callback
    /// }
    /// ```
    #[doc(hidden)]
    pub fn from_borrowed_raw_ptr(ptr: &C::RawContext) -> Self {
        let data = Self::_get_opaque(ptr);
        if data.is_null() {
            panic!("[JSContext] opaque is empty");
        } else {
            let ctx_inner = unsafe { &(*data).ctx_inner };
            if let Some(ctx) = ctx_inner.upgrade() {
                Self { rc: ctx }
            } else {
                panic!("[JSContext] context has been dropped");
            }
        }
    }

    /// Evaluate JavaScript code and return the result
    ///
    /// # Arguments
    /// * `source` - The JavaScript source code to evaluate
    ///
    /// # Returns
    /// * `Ok(T)` - The result of the evaluation if successful
    /// * `Err(RongJSError)` - If evaluation fails or throws an exception
    ///
    /// # Examples
    /// ```rust,no_run
    /// use rong_core::prelude::*;
    ///
    /// fn demo<E: JSEngine + 'static>() -> JSResult<()> {
    ///     let runtime = E::runtime();
    ///     let ctx = runtime.context();
    ///
    ///     let result: i32 = ctx.eval(Source::from_bytes("1 + 2"))?;
    ///     assert_eq!(result, 3);
    ///     Ok(())
    /// }
    /// ```
    pub fn eval<T>(&self, source: Source) -> JSResult<T>
    where
        C::Value: JSObjectOps,
        T: FromJSValue<C::Value>,
    {
        let result = match source.kind() {
            SourceKind::ByteCode(code) => self.rc.inner.run_bytecode(code),
            SourceKind::JavaScript(code) => self.rc.inner.eval(Source::from_bytes(code.clone())),
        };
        result.try_convert::<T>()
    }

    /// Get the global object of the JavaScript context
    ///
    /// # Returns
    /// A JSObject representing the global object
    ///
    /// # Examples
    /// ```rust,no_run
    /// use rong_core::prelude::*;
    ///
    /// fn demo<E: JSEngine + 'static>() -> JSResult<()> {
    ///     let runtime = E::runtime();
    ///     let ctx = runtime.context();
    ///
    ///     let global = ctx.global();
    ///     global.set("myVar", 42)?;
    ///
    ///     let result: i32 = global.get("myVar")?;
    ///     assert_eq!(result, 42);
    ///     Ok(())
    /// }
    /// ```
    pub fn global(&self) -> JSObject<C::Value>
    where
        C::Value: JSTypeOf,
    {
        let raw = self.rc.inner.global();
        JSObject::from_js_value(self, JSValue::from_raw(self, raw)).unwrap()
    }

    fn register_class_with_visibility<JC>(&self, expose_on_global: bool) -> JSResult<()>
    where
        JC: JSClass<C::Value>,
        C::Value: JSObjectOps,
    {
        let registry = self
            .get_class_registry()
            .ok_or_else(|| HostError::new(crate::error::E_INTERNAL, "No Class registry!"))?;

        if registry.borrow().contains_key(&TypeId::of::<JC>()) {
            return Ok(());
        }

        let value = self.rc.inner.register_class::<JC>();

        let constructor = JSValue::from_raw(self, value.clone());
        JC::class_setup(&ClassSetup::new(constructor.clone().into(), self)?)?;
        if expose_on_global {
            let obj = self.global();
            obj.set(JC::NAME, constructor)?;
        }

        registry.borrow_mut().insert(TypeId::of::<JC>(), value);

        Ok(())
    }

    /// Register a JavaScript class for a Rust type.
    ///
    /// This function registers a JavaScript class constructor in the global object
    /// and stores it in the context's class registry. The class can then be used
    /// to create instances in JavaScript.
    ///
    /// ```ignore
    /// // Pseudo-code (requires a JS engine + a type that implements JSClass)
    /// context.register_class::<MyClass>();
    /// ```
    pub fn register_class<JC>(&self) -> JSResult<()>
    where
        JC: JSClass<C::Value>,
        C::Value: JSObjectOps,
    {
        self.register_class_with_visibility::<JC>(true)
    }

    /// Register a JavaScript class without exposing its constructor on the global object.
    ///
    /// This is for Rust-owned interop classes that must exist in the class registry
    /// but should not be directly constructed by JavaScript code.
    pub fn register_hidden_class<JC>(&self) -> JSResult<()>
    where
        JC: JSClass<C::Value>,
        C::Value: JSObjectOps,
    {
        self.register_class_with_visibility::<JC>(false)
    }

    /// Get class registry from context
    pub(crate) fn get_class_registry(&self) -> Option<&RefCell<HashMap<TypeId, C::Value>>> {
        let data = self.get_opaque();
        if data.is_null() {
            None
        } else {
            unsafe { Some(&(*data).registry) }
        }
    }

    pub(crate) fn capture_thrown(&self, value: JSValue<C::Value>) -> ThrownValueHandle {
        let data = self.get_opaque();
        if data.is_null() {
            panic!("[JSContext] opaque is empty");
        }

        let context_id = C::context_id(self.as_ref().as_raw());

        #[cfg(debug_assertions)]
        tracing::debug!(target: "rong", context_id, "capturing thrown JS value");

        let mut store = unsafe { (*data).thrown.try_borrow_mut() }
            .expect("[JSContext] Fatal: ThrownValueStore already borrowed. Recursive error handling detected.");

        store.insert(context_id, value.into_value())
    }

    pub(crate) fn resolve_thrown(&self, handle: ThrownValueHandle) -> Option<JSValue<C::Value>> {
        let data = self.get_opaque();
        if data.is_null() {
            return None;
        }

        let context_id = C::context_id(self.as_ref().as_raw());
        let store = unsafe { (*data).thrown.try_borrow().ok()? };
        store
            .get(context_id, handle)
            .map(|v| JSValue::from_raw(self, v))
    }

    pub(crate) fn take_thrown(&self, handle: ThrownValueHandle) -> Option<JSValue<C::Value>> {
        let data = self.get_opaque();
        if data.is_null() {
            return None;
        }

        let context_id = C::context_id(self.as_ref().as_raw());
        let mut store = unsafe { (*data).thrown.try_borrow_mut().ok()? };
        store
            .take(context_id, handle)
            .map(|v| JSValue::from_raw(self, v))
    }

    pub fn runtime(&self) -> &JSRuntime<C::Runtime> {
        &self.rc.runtime
    }

    /// Register a context-scoped service of a specific type.
    ///
    /// The service is stored by its concrete type. Only one instance of each
    /// service type can be registered for a given JSContext.
    pub fn set_service<T: JSContextService>(&self, service: T) {
        self.rc.services.register::<T>(service);
    }

    /// Get a previously registered context-scoped service by type.
    ///
    /// Returns None if no service of the requested type has been registered.
    pub fn get_service<T: JSContextService>(&self) -> Option<&T> {
        self.rc.services.get::<T>()
    }

    /// Store context-scoped state without implementing `JSContextService`.
    ///
    /// This is intended for simple values that don't need cleanup when the context is dropped.
    /// If you need cleanup, implement `JSContextService` and use `set_service` so `on_shutdown`
    /// can run during context teardown.
    pub fn set_state<T: 'static>(&self, value: T) {
        self.rc.services.register_state(value);
    }

    /// Get context-scoped state previously stored via `set_state`.
    pub fn get_state<T: 'static>(&self) -> Option<&T> {
        self.rc.services.get_state::<T>()
    }

    /// Compile JavaScript source code to bytecode
    ///
    /// # Arguments
    /// * `code` - The JavaScript source code to compile. Accepts:
    ///   - &str: JavaScript source code as string
    ///   - &[u8]: JavaScript source code as bytes
    ///   - String: Owned JavaScript source code
    ///   - `Vec<u8>`: Owned JavaScript source code as bytes
    ///
    /// # Returns
    /// * `Ok(Source)` - Compiled bytecode wrapped in a Source, ready to be evaluated
    /// * `Err(RongJSError)` - If compilation fails
    ///
    /// # Example
    /// ```rust,no_run
    /// use rong_core::prelude::*;
    ///
    /// fn demo<E: JSEngine + 'static>() -> JSResult<()> {
    ///     let runtime = E::runtime();
    ///     let ctx = runtime.context();
    ///
    ///     // From string literal
    ///     let _bytecode = ctx.compile_to_bytecode("function add(a, b) { return a + b; }")?;
    ///
    ///     // From bytes
    ///     let _bytecode = ctx.compile_to_bytecode(b"let x = 1;")?;
    ///
    ///     Ok(())
    /// }
    /// ```
    pub fn compile_to_bytecode<T: AsRef<[u8]>>(&self, code: T) -> JSResult<Source> {
        self.rc
            .inner
            .compile_to_bytecode(Source::from_bytes(code.as_ref()))
            .map(Source::from_bytecode)
    }

    /// Evaluate JavaScript code and handle both Promise and immediate results
    ///
    /// This function evaluates the provided JavaScript source code and:
    /// 1. If the result is a Promise, waits for it to resolve and returns the resolved value
    /// 2. If the result is not a Promise, returns it immediately
    ///
    /// # Arguments
    /// * `source` - The JavaScript source code to evaluate
    ///
    /// # Returns
    /// * `Ok(T)` - The result of the evaluation or resolved Promise value
    /// * `Err(RongJSError)` - If evaluation fails, throws an exception, or Promise rejects
    pub async fn eval_async<T>(&self, source: Source) -> JSResult<T>
    where
        C::Value: JSTypeOf + JSObjectOps + 'static,
        T: FromJSValue<C::Value> + 'static,
    {
        let result = match source.kind() {
            SourceKind::ByteCode(code) => self.rc.inner.run_bytecode(code),
            SourceKind::JavaScript(code) => self.rc.inner.eval(Source::from_bytes(code.clone())),
        };

        if result.is_promise() {
            let promise = Promise::from_js_value(self, JSValue::from_raw(self, result))?;
            promise.into_future::<T>().await
        } else {
            result.try_convert::<T>()
        }
    }

    fn get_opaque(&self) -> *mut ContextOpaque<C::Value> {
        let key = self.rc.inner.as_raw();
        Self::_get_opaque(key)
    }

    fn _get_opaque(raw_ctx: &C::RawContext) -> *mut ContextOpaque<C::Value> {
        let key = C::context_id(raw_ctx);
        if let Some(opaque_ptr) = CTX_OPAQUE.read().unwrap().get(&key) {
            *opaque_ptr as *mut ContextOpaque<C::Value>
        } else {
            std::ptr::null_mut()
        }
    }
}

/// Container to hold the context-specific data for a JSContext.
///
/// # Fields
/// - `registry`: A pointer to a RefCell containing a HashMap that maps TypeId to type that implements JSValueImpl
/// - `ctx_inner`: Weak reference to the JSContextInner used to build JSContext from callback case
struct ContextOpaque<V: JSValueImpl> {
    registry: RefCell<HashMap<TypeId, V>>,
    ctx_inner: Weak<JSContextInner<V::Context>>,
    thrown: RefCell<ThrownValueStore<V>>,
}

impl<V: JSValueImpl> ContextOpaque<V> {
    fn new(ctx_inner: Weak<JSContextInner<V::Context>>) -> Box<Self> {
        Box::new(Self {
            registry: RefCell::new(HashMap::new()),
            ctx_inner,
            thrown: RefCell::new(ThrownValueStore::new()),
        })
    }
}

// Global HashMap to store ContextOpaque<V>
// Like JavaScriptCore engine, it does not provide API to save opaque on JS Context,
// that's why we introduce general solution CTX_OPAQUE
static CTX_OPAQUE: LazyLock<RwLock<HashMap<usize, usize>>> =
    LazyLock::new(|| RwLock::new(HashMap::new()));

impl<C: JSContextImpl> Drop for JSContext<C> {
    fn drop(&mut self) {
        if Rc::strong_count(&self.rc) == 1 {
            // First, shutdown all context-scoped services.
            self.rc.services.shutdown();

            let raw_ctx = self.rc.inner.as_raw();
            let key = C::context_id(raw_ctx);
            let data = CTX_OPAQUE
                .write()
                .unwrap()
                .remove(&key)
                .map(|ptr| ptr as *mut ContextOpaque<C::Value>)
                .unwrap_or(std::ptr::null_mut());

            if !data.is_null() {
                unsafe {
                    // cleanup class registry
                    let registry = &(*data).registry;
                    registry.borrow_mut().clear();

                    // cleanup ContextOpaque
                    let _ = Box::from_raw(data);
                }
            }
        }
    }
}

impl<C: JSContextImpl> Clone for JSContext<C> {
    fn clone(&self) -> Self {
        Self {
            rc: Rc::clone(&self.rc),
        }
    }
}

impl<C: JSContextImpl> std::fmt::Debug for JSContext<C> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "JSContext {{ address: {:p}, ref_count: {} }}",
            self as *const _,
            Rc::strong_count(&self.rc)
        )
    }
}