Skip to main content

wry_bindgen_core/
thread_local.rs

1//! Lazily-initialized, cached runtime-local JavaScript values.
2
3use alloc::boxed::Box;
4
5use crate::runtime::with_backend;
6
7/// A runtime-local accessor for lazily initialized JavaScript values.
8///
9/// This type provides safe access to cached JavaScript global values, ensuring
10/// the value is initialized on first access in the active runtime.
11///
12/// # Example
13///
14/// ```ignore
15/// #[wasm_bindgen]
16/// extern "C" {
17///     #[wasm_bindgen(thread_local_v2, js_name = window)]
18///     pub static WINDOW: Window;
19/// }
20///
21/// WINDOW.with(|window| {
22///     let doc = window.document();
23/// });
24/// ```
25pub struct LazyCell<T: 'static> {
26    init: fn() -> T,
27}
28
29impl<T> LazyCell<T> {
30    /// Create a new `LazyCell`.
31    #[doc(hidden)]
32    pub const fn new(init: fn() -> T) -> Self {
33        Self { init }
34    }
35
36    /// Return the cached value, initializing it if needed.
37    pub fn force(&self) -> &'static T {
38        let init = self.init;
39        // Take the value out of the runtime, initializing it if it isn't there
40        // yet. We initialize outside the runtime borrow because init() may
41        // re-enter the runtime to access other thread locals.
42        //
43        // We never drop js thread locals because:
44        // 1. The destructor only has an effect when the webview still exists and it should now be gone
45        // 2. It would rely on the thread local being dropped before the runtime is dropped, which relies on the drop order of
46        // different thread locals
47        let value = match with_backend(|runtime| runtime.take_thread_local_box(self)) {
48            Some(value) => *value.downcast::<&'static T>().expect("type mismatch"),
49            None => Box::leak(Box::new(init())) as &'static T,
50        };
51        with_backend(|runtime| {
52            runtime.insert_thread_local_box(self, Box::new(value));
53        });
54        value
55    }
56}
57
58impl<T> core::ops::Deref for LazyCell<T> {
59    type Target = T;
60
61    fn deref(&self) -> &T {
62        Self::force(self)
63    }
64}
65
66/// Backwards-compatible name used by generated `thread_local_v2` bindings.
67pub struct JsThreadLocal<T: 'static> {
68    inner: LazyCell<T>,
69}
70
71impl<T> JsThreadLocal<T> {
72    /// Create a new `JsThreadLocal`.
73    #[doc(hidden)]
74    pub const fn new(init: fn() -> T) -> Self {
75        Self {
76            inner: LazyCell::new(init),
77        }
78    }
79
80    /// Run a closure with access to the cached value.
81    pub fn with<F, R>(&'static self, f: F) -> R
82    where
83        F: FnOnce(&T) -> R,
84    {
85        f(LazyCell::force(&self.inner))
86    }
87}