wry_bindgen/
lazy.rs

1//! Lazy initialization support for global JavaScript values
2//!
3//! This module provides types for lazily initializing and caching JavaScript
4//! global values, similar to wasm-bindgen's thread_local_v2 support.
5
6use crate::batch::with_runtime;
7use core::{mem::ManuallyDrop, panic::Location};
8
9/// A thread-local accessor for lazily initialized JavaScript values.
10///
11/// This type provides safe access to cached JavaScript global values,
12/// ensuring the value is initialized on first access. You can access
13/// the value directly via `Deref`.
14///
15/// # Example
16///
17/// ```ignore
18/// #[wasm_bindgen]
19/// extern "C" {
20///     #[wasm_bindgen(thread_local_v2, js_name = window)]
21///     pub static WINDOW: Window;
22/// }
23///
24/// // Access the cached window value directly
25/// let doc = WINDOW.document();
26/// ```
27pub struct JsThreadLocal<T: 'static> {
28    key: ThreadLocalKey<'static>,
29    init: fn() -> T,
30}
31
32impl<T> JsThreadLocal<T> {
33    /// Create a new `JsThreadLocal` from a `LocalKey`.
34    #[doc(hidden)]
35    #[track_caller]
36    pub const fn new(init: fn() -> T, index: u32) -> Self {
37        let caller = Location::caller();
38        Self {
39            key: ThreadLocalKey {
40                file: caller.file(),
41                line: caller.line(),
42                column: caller.column(),
43                index,
44            },
45            init,
46        }
47    }
48
49    /// Run a closure with access to the cached value.
50    pub fn with<F, R>(&'static self, f: F) -> R
51    where
52        F: FnOnce(&T) -> R,
53    {
54        let init = self.init;
55        // Check if it exists in the runtime
56        let initialized = with_runtime(|runtime| runtime.has_thread_local(self.key));
57        if !initialized {
58            // Initialize it before we open the runtime borrow
59            let value = init();
60            with_runtime(|runtime| {
61                // We never drop js thread locals because:
62                // 1. The destructor only has an effect when the webview still exists and it should now be gone
63                // 2. It would rely on the thread local being dropped before the runtime is dropped, which relies on the drop order of
64                // different thread locals
65                runtime.insert_thread_local(self.key, ManuallyDrop::new(value));
66            });
67        }
68        // Now that we know it exists, access it
69        let value: ManuallyDrop<T> = with_runtime(|runtime| runtime.take_thread_local(self.key));
70        // We can't hold the runtime borrow while calling f, so we have to
71        // move the value out temporarily and put it back afterwards. The f
72        // closure could re-enter the runtime to access other thread locals.
73        let result = f(&value);
74        // Put it back
75        with_runtime(|runtime| {
76            runtime.insert_thread_local(self.key, value);
77        });
78        result
79    }
80}
81
82/// A key used to identify a thread-local variable.
83#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
84pub(crate) struct ThreadLocalKey<'a> {
85    /// The file name
86    file: &'a str,
87
88    /// The line number
89    line: u32,
90
91    /// The column number
92    column: u32,
93
94    /// The index of the signal in the file - used to disambiguate macro calls
95    index: u32,
96}