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