Skip to main content

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>;