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}