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}