diff --git a/src/core/element_maybe_signal.rs b/src/core/element_maybe_signal.rs index 780ed55..f65423e 100644 --- a/src/core/element_maybe_signal.rs +++ b/src/core/element_maybe_signal.rs @@ -171,7 +171,7 @@ where let _ = target; Self::Static(None) } else { - Self::Static(document().query_selector(target).unwrap_or_default()) + Self::Static(document().query_selector(target).unwrap_or_default().map(SendWrapper::new)) }} } } @@ -197,7 +197,7 @@ macro_rules! impl_from_signal_string { Self::Dynamic(Signal::derive(|| None)) } else { Self::Dynamic( - Signal::derive(move || document().query_selector(&signal.get()).unwrap_or_default()), + Signal::derive(move || document().query_selector(&signal.get()).unwrap_or_default().map(SendWrapper::new)), ) }} } @@ -212,8 +212,8 @@ impl_from_signal_string!(Memo); impl_from_signal_string!(Signal<&str>); impl_from_signal_string!(ReadSignal<&str>); -impl_from_signal_string!(RwSignal<&str>); -impl_from_signal_string!(Memo<&str>); +impl_from_signal_string!(RwSignal<&'static str>); +impl_from_signal_string!(Memo<&'static str>); // From signal /////////////////////////////////////////////////////////////// diff --git a/src/core/elements_maybe_signal.rs b/src/core/elements_maybe_signal.rs index 069cd7b..4a5a153 100644 --- a/src/core/elements_maybe_signal.rs +++ b/src/core/elements_maybe_signal.rs @@ -156,7 +156,7 @@ where let mut list = Vec::with_capacity(node_list.length() as usize); for i in 0..node_list.length() { let node = node_list.get(i).expect("checked the range"); - list.push(Some(node)); + list.push(Some(SendWrapper::new(node))); } Self::Static(list) diff --git a/src/lib.rs b/src/lib.rs index aaf827c..617c0b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,7 +68,7 @@ mod use_preferred_dark; mod use_raf_fn; mod use_resize_observer; mod use_scroll; -mod use_service_worker; +// mod use_service_worker; mod use_sorted; mod use_supported; mod use_throttle_fn; @@ -134,7 +134,7 @@ pub use use_preferred_dark::*; pub use use_raf_fn::*; pub use use_resize_observer::*; pub use use_scroll::*; -pub use use_service_worker::*; +// pub use use_service_worker::*; pub use use_sorted::*; pub use use_supported::*; pub use use_throttle_fn::*; diff --git a/src/on_click_outside.rs b/src/on_click_outside.rs index d3868c9..db5e2d9 100644 --- a/src/on_click_outside.rs +++ b/src/on_click_outside.rs @@ -149,7 +149,7 @@ where let ignore = ignore.get_untracked(); ignore.into_iter().flatten().any(|element| { - let element: web_sys::EventTarget = element.into(); + let element: web_sys::EventTarget = element.take().into(); event_target::(event) == element || event.composed_path().includes(element.as_ref(), 0) diff --git a/src/storage/use_storage.rs b/src/storage/use_storage.rs index 9b709c8..93a26d1 100644 --- a/src/storage/use_storage.rs +++ b/src/storage/use_storage.rs @@ -6,6 +6,7 @@ use codee::{CodecError, Decoder, Encoder}; use default_struct_builder::DefaultBuilder; use leptos::prelude::wrappers::read::Signal; use leptos::prelude::*; +use send_wrapper::SendWrapper; use std::sync::Arc; use thiserror::Error; use wasm_bindgen::JsValue; @@ -202,7 +203,7 @@ where // Get storage API let storage = storage_type .into_storage() - .map_err(UseStorageError::StorageNotAvailable) + .map_err(|e| UseStorageError::StorageNotAvailable(SendWrapper::new(e))) .and_then(|s| s.ok_or(UseStorageError::StorageReturnedNone)); let storage = handle_error(&on_error, storage); @@ -226,7 +227,7 @@ where ) .expect("failed to create custom storage event"), ) - .map_err(UseStorageError::NotifyItemChangedFailed); + .map_err(|e| UseStorageError::NotifyItemChangedFailed(SendWrapper::new(e))); let _ = handle_error(&on_error, result); }) } @@ -238,14 +239,14 @@ where let key = key.as_ref().to_owned(); let on_error = on_error.to_owned(); - move || { + SendWrapper::new(move || { let fetched = storage .to_owned() .and_then(|storage| { // Get directly from storage let result = storage .get_item(&key) - .map_err(UseStorageError::GetItemFailed); + .map_err(|e| UseStorageError::GetItemFailed(SendWrapper::new(e))); handle_error(&on_error, result) }) .unwrap_or_default() // Drop handled Err(()) @@ -270,20 +271,25 @@ where // Revert to default None => set_data.set(default.clone()), }; - } + }) }; // Fires when storage needs to be fetched - let notify = Trigger::new(); + let notify = ArcTrigger::new(); // Refetch from storage. Keeps track of how many times we've been notified. Does not increment for calls to set_data - let notify_id = Memo::::new(move |prev| { - notify.track(); - match prev { - None => 1, // Avoid async fetch of initial value - Some(prev) => { - fetch_from_storage(); - prev + 1 + let notify_id = Memo::::new({ + let notify = notify.clone(); + let fetch_from_storage = fetch_from_storage.clone(); + + move |prev| { + notify.track(); + match prev { + None => 1, // Avoid async fetch of initial value + Some(prev) => { + fetch_from_storage(); + prev + 1 + } } } }); @@ -308,9 +314,9 @@ where .map_err(|e| UseStorageError::ItemCodecError(CodecError::Encode(e))) .and_then(|enc_value| { // Set storage -- sends a global event - storage - .set_item(&key, &enc_value) - .map_err(UseStorageError::SetItemFailed) + storage.set_item(&key, &enc_value).map_err(|e| { + UseStorageError::SetItemFailed(SendWrapper::new(e)) + }) }); let result = handle_error(&on_error, result); // Send internal storage event @@ -323,31 +329,42 @@ where ); } - // Fetch initial value - if delay_during_hydration && leptos::leptos_dom::HydrationCtx::is_hydrating() { - request_animation_frame(fetch_from_storage.clone()); - } else { - fetch_from_storage(); - } + // TODO: solve for 0.7 + // // Fetch initial value + // if delay_during_hydration && leptos::leptos_dom::HydrationCtx::is_hydrating() { + // request_animation_frame(fetch_from_storage.clone()); + // } else { + // fetch_from_storage(); + // } + request_animation_frame({ + let fetch_from_storage = fetch_from_storage.clone(); + #[allow(clippy::redundant_closure)] + move || fetch_from_storage() + }); if listen_to_storage_changes { let check_key = key.as_ref().to_owned(); + let storage_notify = notify.clone(); + let custom_notify = notify.clone(); + // Listen to global storage events let _ = use_event_listener(use_window(), leptos::ev::storage, move |ev| { let ev_key = ev.key(); // Key matches or all keys deleted (None) if ev_key == Some(check_key.clone()) || ev_key.is_none() { - notify.notify() + storage_notify.trigger() } }); // Listen to internal storage events let check_key = key.as_ref().to_owned(); let _ = use_event_listener( use_window(), - ev::Custom::new(INTERNAL_STORAGE_EVENT), - move |ev: web_sys::CustomEvent| { - if Some(check_key.clone()) == ev.detail().as_string() { - notify.notify() + leptos::ev::Custom::new(INTERNAL_STORAGE_EVENT), + { + move |ev: web_sys::CustomEvent| { + if Some(check_key.clone()) == ev.detail().as_string() { + custom_notify.trigger() + } } }, ); @@ -361,9 +378,9 @@ where // Delete directly from storage let result = storage .remove_item(&key) - .map_err(UseStorageError::RemoveItemFailed); + .map_err(|e| UseStorageError::RemoveItemFailed(SendWrapper::new(e))); let _ = handle_error(&on_error, result); - notify.notify(); + notify.trigger(); dispatch_storage_event(); }); } @@ -377,17 +394,17 @@ where #[derive(Error, Debug)] pub enum UseStorageError { #[error("storage not available")] - StorageNotAvailable(JsValue), + StorageNotAvailable(SendWrapper), #[error("storage not returned from window")] StorageReturnedNone, #[error("failed to get item")] - GetItemFailed(JsValue), + GetItemFailed(SendWrapper), #[error("failed to set item")] - SetItemFailed(JsValue), + SetItemFailed(SendWrapper), #[error("failed to delete item")] - RemoveItemFailed(JsValue), + RemoveItemFailed(SendWrapper), #[error("failed to notify item changed")] - NotifyItemChangedFailed(JsValue), + NotifyItemChangedFailed(SendWrapper), #[error("failed to encode / decode item value")] ItemCodecError(CodecError), } @@ -400,7 +417,7 @@ where { // Callback for when an error occurs #[builder(skip)] - on_error: Arc)>, + on_error: Arc) + Send + Sync>, // Whether to continuously listen to changes from browser storage listen_to_storage_changes: bool, // Initial value to use when the storage key is not set @@ -418,7 +435,7 @@ where /// Calls the on_error callback with the given error. Removes the error from the Result to avoid double error handling. #[cfg(not(feature = "ssr"))] fn handle_error( - on_error: &Arc)>, + on_error: &Arc) + Send + Sync>, result: Result>, ) -> Result { result.map_err(|err| (on_error)(err)) @@ -438,7 +455,10 @@ impl Default for UseStorageOptions { impl UseStorageOptions { /// Optional callback whenever an error occurs. - pub fn on_error(self, on_error: impl Fn(UseStorageError) + 'static) -> Self { + pub fn on_error( + self, + on_error: impl Fn(UseStorageError) + Send + Sync + 'static, + ) -> Self { Self { on_error: Arc::new(on_error), ..self diff --git a/src/use_color_mode.rs b/src/use_color_mode.rs index d233475..d1d3194 100644 --- a/src/use_color_mode.rs +++ b/src/use_color_mode.rs @@ -9,8 +9,8 @@ use leptos::prelude::wrappers::read::Signal; use leptos::prelude::*; use std::fmt::{Display, Formatter}; use std::marker::PhantomData; -use std::rc::Rc; use std::str::FromStr; +use std::sync::Arc; use wasm_bindgen::JsCast; /// Reactive color mode (dark / light / customs) with auto data persistence. @@ -209,7 +209,7 @@ where if initial_value_from_url_param_to_storage { set_store.set(value); } else { - set_store.set_untracked(value); + *set_store.write_untracked() = value; } } @@ -279,7 +279,7 @@ where }; let on_changed = move |mode: ColorMode| { - on_changed(mode, Rc::new(default_on_changed.clone())); + on_changed(mode, Arc::new(default_on_changed.clone())); }; Effect::new({ @@ -427,7 +427,7 @@ where /// To get the default behaviour back you can call the provided `default_handler` function. /// It takes two parameters: /// - `mode: ColorMode`: The color mode to change to. - /// -`default_handler: Rc`: The default handler that would have been called if the `on_changed` handler had not been specified. + /// -`default_handler: Arc`: The default handler that would have been called if the `on_changed` handler had not been specified. on_changed: OnChangedFn, /// When provided, `useStorage` will be skipped. @@ -476,7 +476,7 @@ where _marker: PhantomData, } -type OnChangedFn = Rc)>; +type OnChangedFn = Arc) + Send + Sync>; impl Default for UseColorModeOptions<&'static str, web_sys::Element> { fn default() -> Self { @@ -487,7 +487,7 @@ impl Default for UseColorModeOptions<&'static str, web_sys::Element> { initial_value_from_url_param: None, initial_value_from_url_param_to_storage: false, custom_modes: vec![], - on_changed: Rc::new(move |mode, default_handler| (default_handler)(mode)), + on_changed: Arc::new(move |mode, default_handler| (default_handler)(mode)), storage_signal: None, storage_key: "leptos-use-color-scheme".into(), storage: StorageType::default(), diff --git a/src/use_cookie.rs b/src/use_cookie.rs index e624d11..11c3b9f 100644 --- a/src/use_cookie.rs +++ b/src/use_cookie.rs @@ -7,7 +7,7 @@ pub use cookie::SameSite; use cookie::{Cookie, CookieJar}; use default_struct_builder::DefaultBuilder; use leptos::prelude::*; -use std::rc::Rc; +use std::sync::Arc; /// SSR-friendly and reactive cookie access. /// @@ -143,7 +143,7 @@ use std::rc::Rc; pub fn use_cookie(cookie_name: &str) -> (Signal>, WriteSignal>) where C: Encoder + Decoder, - T: Clone, + T: Clone + Send + Sync, { use_cookie_with_options::(cookie_name, UseCookieOptions::default()) } @@ -155,7 +155,7 @@ pub fn use_cookie_with_options( ) -> (Signal>, WriteSignal>) where C: Encoder + Decoder, - T: Clone, + T: Clone + Send + Sync, { let UseCookieOptions { max_age, @@ -189,7 +189,7 @@ where let jar = StoredValue::new(CookieJar::new()); if !has_expired { - let ssr_cookies_header_getter = Rc::clone(&ssr_cookies_header_getter); + let ssr_cookies_header_getter = Arc::clone(&ssr_cookies_header_getter); jar.update_value(|jar| { if let Some(new_jar) = load_and_parse_cookie_jar(ssr_cookies_header_getter) { @@ -229,8 +229,8 @@ where let on_cookie_change = { let cookie_name = cookie_name.to_owned(); - let ssr_cookies_header_getter = Rc::clone(&ssr_cookies_header_getter); - let on_error = Rc::clone(&on_error); + let ssr_cookies_header_getter = Arc::clone(&ssr_cookies_header_getter); + let on_error = Arc::clone(&on_error); let domain = domain.clone(); let path = path.clone(); @@ -265,7 +265,7 @@ where same_site, secure, http_only, - Rc::clone(&ssr_cookies_header_getter), + Arc::clone(&ssr_cookies_header_getter), ); }); @@ -288,7 +288,7 @@ where // listen to cookie changes from the broadcast channel Effect::new({ - let ssr_cookies_header_getter = Rc::clone(&ssr_cookies_header_getter); + let ssr_cookies_header_getter = Arc::clone(&ssr_cookies_header_getter); let cookie_name = cookie_name.to_owned(); move |_| { @@ -299,7 +299,7 @@ where match C::decode(&message) { Ok(value) => { let ssr_cookies_header_getter = - Rc::clone(&ssr_cookies_header_getter); + Arc::clone(&ssr_cookies_header_getter); jar.update_value(|jar| { update_client_cookie_jar( @@ -325,7 +325,7 @@ where } } else { let cookie_name = cookie_name.clone(); - let ssr_cookies_header_getter = Rc::clone(&ssr_cookies_header_getter); + let ssr_cookies_header_getter = Arc::clone(&ssr_cookies_header_getter); jar.update_value(|jar| { update_client_cookie_jar( @@ -467,14 +467,14 @@ pub struct UseCookieOptions { /// Getter function to return the string value of the cookie header. /// When you use one of the features `"axum"`, `"actix"` or `"spin"` there's a valid default implementation provided. - ssr_cookies_header_getter: Rc Option>, + ssr_cookies_header_getter: Arc Option + Send + Sync>, /// Function to add a set cookie header to the response on the server. /// When you use one of the features `"axum"`, `"actix"` or `"spin"` there's a valid default implementation provided. - ssr_set_cookie: Rc, + ssr_set_cookie: Arc, /// Callback for encoding/decoding errors. Defaults to logging the error to the console. - on_error: Rc)>, + on_error: Arc) + Send + Sync>, } impl Default for UseCookieOptions { @@ -490,7 +490,7 @@ impl Default for UseCookieOptions { domain: None, path: None, same_site: None, - ssr_cookies_header_getter: Rc::new(move || { + ssr_cookies_header_getter: Arc::new(move || { #[cfg(feature = "ssr")] { #[cfg(all(feature = "actix", feature = "axum"))] @@ -564,7 +564,7 @@ impl Default for UseCookieOptions { #[cfg(not(feature = "ssr"))] None }), - ssr_set_cookie: Rc::new(|cookie: &Cookie| { + ssr_set_cookie: Arc::new(|cookie: &Cookie| { #[cfg(feature = "ssr")] { #[cfg(feature = "actix")] @@ -615,7 +615,7 @@ impl Default for UseCookieOptions { let _ = cookie; }), - on_error: Rc::new(|_| { + on_error: Arc::new(|_| { error!("cookie (de-/)serialization error"); }), } @@ -623,7 +623,7 @@ impl Default for UseCookieOptions { } fn read_cookies_string( - ssr_cookies_header_getter: Rc Option>, + ssr_cookies_header_getter: Arc Option + Send + Sync>, ) -> Option { let cookies; @@ -646,71 +646,80 @@ fn read_cookies_string( cookies } -fn handle_expiration(delay: Option, set_cookie: WriteSignal>) { +fn handle_expiration(delay: Option, set_cookie: WriteSignal>) +where + T: Send + Sync + 'static, +{ if let Some(delay) = delay { #[cfg(not(feature = "ssr"))] { use leptos::leptos_dom::helpers::TimeoutHandle; - use std::cell::Cell; - use std::cell::RefCell; + use std::sync::{atomic::AtomicI32, Mutex}; // The maximum value allowed on a timeout delay. // Reference: https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#maximum_delay_value const MAX_TIMEOUT_DELAY: i64 = 2_147_483_647; - let timeout = Rc::new(Cell::new(None::)); - let elapsed = Rc::new(Cell::new(0)); + let timeout = Arc::new(Mutex::new(None::)); + let elapsed = Arc::new(AtomicI32::new(0)); on_cleanup({ - let timeout = Rc::clone(&timeout); + let timeout = Arc::clone(&timeout); move || { - if let Some(timeout) = timeout.take() { + if let Some(timeout) = timeout.lock().unwrap().take() { timeout.clear(); } } }); - let create_expiration_timeout = Rc::new(RefCell::new(None::>)); + let create_expiration_timeout = + Arc::new(Mutex::new(None::>)); - create_expiration_timeout.replace(Some(Box::new({ - let timeout = Rc::clone(&timeout); - let elapsed = Rc::clone(&elapsed); - let create_expiration_timeout = Rc::clone(&create_expiration_timeout); + *create_expiration_timeout.lock().unwrap() = Some(Box::new({ + let timeout = Arc::clone(&timeout); + let elapsed = Arc::clone(&elapsed); + let create_expiration_timeout = Arc::clone(&create_expiration_timeout); move || { - if let Some(timeout) = timeout.take() { + if let Some(timeout) = timeout.lock().unwrap().take() { timeout.clear(); } - let time_remaining = delay - elapsed.get(); + let time_remaining = + delay - elapsed.load(std::sync::atomic::Ordering::Relaxed) as i64; let timeout_length = time_remaining.min(MAX_TIMEOUT_DELAY); - let elapsed = Rc::clone(&elapsed); - let create_expiration_timeout = Rc::clone(&create_expiration_timeout); + let elapsed = Arc::clone(&elapsed); + let create_expiration_timeout = Arc::clone(&create_expiration_timeout); - timeout.set( - set_timeout_with_handle( - move || { - elapsed.set(elapsed.get() + timeout_length); - if elapsed.get() < delay { - if let Some(create_expiration_timeout) = - &*create_expiration_timeout.borrow() - { - create_expiration_timeout(); - } - return; + *timeout.lock().unwrap() = set_timeout_with_handle( + move || { + let elapsed = elapsed.fetch_add( + timeout_length as i32, + std::sync::atomic::Ordering::Relaxed, + ) as i64 + + timeout_length; + + if elapsed < delay { + if let Some(create_expiration_timeout) = + create_expiration_timeout.lock().unwrap().as_ref() + { + create_expiration_timeout(); } + return; + } - set_cookie.set(None); - }, - std::time::Duration::from_millis(timeout_length as u64), - ) - .ok(), - ); + set_cookie.set(None); + }, + std::time::Duration::from_millis(timeout_length as u64), + ) + .ok(); } - }))); + })); - if let Some(create_expiration_timeout) = &*create_expiration_timeout.borrow() { + if let Some(create_expiration_timeout) = + create_expiration_timeout.lock().unwrap().as_ref() + { create_expiration_timeout(); }; } @@ -735,7 +744,7 @@ fn write_client_cookie( same_site: Option, secure: bool, http_only: bool, - ssr_cookies_header_getter: Rc Option>, + ssr_cookies_header_getter: Arc Option + Send + Sync>, ) { use wasm_bindgen::JsCast; @@ -771,7 +780,7 @@ fn update_client_cookie_jar( same_site: Option, secure: bool, http_only: bool, - ssr_cookies_header_getter: Rc Option>, + ssr_cookies_header_getter: Arc Option + Send + Sync>, ) { if let Some(new_jar) = load_and_parse_cookie_jar(ssr_cookies_header_getter) { *jar = new_jar; @@ -859,7 +868,7 @@ fn write_server_cookie( same_site: Option, secure: bool, http_only: bool, - ssr_set_cookie: Rc, + ssr_set_cookie: Arc, ) { if let Some(value) = value { let cookie: Cookie = build_cookie_from_options( @@ -877,7 +886,7 @@ fn write_server_cookie( } fn load_and_parse_cookie_jar( - ssr_cookies_header_getter: Rc Option>, + ssr_cookies_header_getter: Arc Option + Send + Sync>, ) -> Option { read_cookies_string(ssr_cookies_header_getter).map(|cookies| { let mut jar = CookieJar::new(); diff --git a/src/use_device_orientation.rs b/src/use_device_orientation.rs index d2db892..f02ee54 100644 --- a/src/use_device_orientation.rs +++ b/src/use_device_orientation.rs @@ -1,5 +1,6 @@ use cfg_if::cfg_if; use leptos::prelude::wrappers::read::Signal; +use send_wrapper::SendWrapper; /// Reactive [DeviceOrientationEvent](https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent). /// Provide web developers with information from the physical orientation of @@ -67,7 +68,8 @@ pub fn use_device_orientation() -> UseDeviceOrientationReturn { .once(false), ); - on_cleanup(cleanup); + let cleanup = SendWrapper::new(cleanup); + on_cleanup(move || cleanup()); } }} diff --git a/src/use_drop_zone.rs b/src/use_drop_zone.rs index f10a88d..e88b345 100644 --- a/src/use_drop_zone.rs +++ b/src/use_drop_zone.rs @@ -90,12 +90,12 @@ where let update_files = move |event: &web_sys::DragEvent| { if let Some(data_transfer) = event.data_transfer() { - let files: Vec = data_transfer + let files: Vec<_> = data_transfer .files() .map(|f| js_sys::Array::from(&f).to_vec()) .unwrap_or_default() .into_iter() - .map(web_sys::File::from) + .map(|f| SendWrapper::new(web_sys::File::from(f))) .collect(); set_files.update(move |f| *f = files); @@ -113,7 +113,11 @@ where let _z = SpecialNonReactiveZone::enter(); on_enter(UseDropZoneEvent { - files: files.get_untracked(), + files: files + .get_untracked() + .into_iter() + .map(SendWrapper::take) + .collect(), event, }); }); @@ -126,7 +130,11 @@ where let _z = SpecialNonReactiveZone::enter(); on_over(UseDropZoneEvent { - files: files.get_untracked(), + files: files + .get_untracked() + .into_iter() + .map(SendWrapper::take) + .collect(), event, }); }); @@ -144,7 +152,11 @@ where let _z = SpecialNonReactiveZone::enter(); on_leave(UseDropZoneEvent { - files: files.get_untracked(), + files: files + .get_untracked() + .into_iter() + .map(SendWrapper::take) + .collect(), event, }); }); @@ -160,7 +172,11 @@ where let _z = SpecialNonReactiveZone::enter(); on_drop(UseDropZoneEvent { - files: files.get_untracked(), + files: files + .get_untracked() + .into_iter() + .map(SendWrapper::take) + .collect(), event, }); }); diff --git a/src/use_event_source.rs b/src/use_event_source.rs index e857a10..9909c4f 100644 --- a/src/use_event_source.rs +++ b/src/use_event_source.rs @@ -5,8 +5,8 @@ use default_struct_builder::DefaultBuilder; use leptos::prelude::diagnostics::SpecialNonReactiveZone; use leptos::prelude::*; use send_wrapper::SendWrapper; -use std::cell::Cell; use std::marker::PhantomData; +use std::sync::atomic::{AtomicBool, AtomicU32}; use std::sync::Arc; use std::time::Duration; use thiserror::Error; @@ -154,8 +154,8 @@ where let (event_source, set_event_source) = signal(None::>); let (error, set_error) = signal(None::>); - let explicitly_closed = Arc::new(Cell::new(false)); - let retried = Arc::new(Cell::new(0)); + let explicitly_closed = Arc::new(AtomicBool::new(false)); + let retried = Arc::new(AtomicU32::new(0)); let set_data_from_string = move |data_string: Option| { if let Some(data_string) = data_string { @@ -174,7 +174,7 @@ where event_source.close(); set_event_source.set(None); set_ready_state.set(ConnectionReadyState::Closed); - explicitly_closed.set(true); + explicitly_closed.store(true, std::sync::atomic::Ordering::Relaxed); } } }; @@ -188,7 +188,7 @@ where move || { use wasm_bindgen::prelude::*; - if explicitly_closed.get() { + if explicitly_closed.load(std::sync::atomic::Ordering::Relaxed) { return; } @@ -222,14 +222,15 @@ where // only reconnect if EventSource isn't reconnecting by itself // this is the case when the connection is closed (readyState is 2) if es.ready_state() == 2 - && !explicitly_closed.get() + && !explicitly_closed.load(std::sync::atomic::Ordering::Relaxed) && matches!(reconnect_limit, ReconnectLimit::Limited(_)) { es.close(); - retried.set(retried.get() + 1); + let retried_value = + retried.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1; - if reconnect_limit.is_exceeded_by(retried.get()) { + if reconnect_limit.is_exceeded_by(retried_value as u64) { set_timeout( move || { if let Some(init) = init.get_value() { @@ -281,8 +282,8 @@ where move || { close(); - explicitly_closed.set(false); - retried.set(0); + explicitly_closed.store(false, std::sync::atomic::Ordering::Relaxed); + retried.store(0, std::sync::atomic::Ordering::Relaxed); if let Some(init) = init.get_value() { init(); } @@ -326,7 +327,7 @@ where reconnect_interval: u64, /// On maximum retry times reached. - on_failed: Arc, + on_failed: Arc, /// If `true` the `EventSource` connection will immediately be opened when calling this function. /// If `false` you have to manually call the `open` function. diff --git a/src/use_infinite_scroll.rs b/src/use_infinite_scroll.rs index 3239ac0..4059c64 100644 --- a/src/use_infinite_scroll.rs +++ b/src/use_infinite_scroll.rs @@ -9,8 +9,9 @@ use gloo_timers::future::sleep; use leptos::prelude::diagnostics::SpecialNonReactiveZone; use leptos::prelude::wrappers::read::Signal; use leptos::prelude::*; +use send_wrapper::SendWrapper; use std::future::Future; -use std::rc::Rc; +use std::sync::Arc; use std::time::Duration; use wasm_bindgen::JsCast; @@ -55,7 +56,7 @@ pub fn use_infinite_scroll(el: El, on_load_more: LFn) -> Signa where El: Into> + Clone + 'static, T: Into + Clone + 'static, - LFn: Fn(ScrollState) -> LFut + 'static, + LFn: Fn(ScrollState) -> LFut + Send + Sync + 'static, LFut: Future, { use_infinite_scroll_with_options(el, on_load_more, UseInfiniteScrollOptions::default()) @@ -70,7 +71,7 @@ pub fn use_infinite_scroll_with_options( where El: Into> + Clone + 'static, T: Into + Clone + 'static, - LFn: Fn(ScrollState) -> LFut + 'static, + LFn: Fn(ScrollState) -> LFut + Send + Sync + 'static, LFut: Future, { let UseInfiniteScrollOptions { @@ -117,20 +118,22 @@ where let el = el.into(); if el.is_instance_of::() || el.is_instance_of::() { - document() - .document_element() - .expect("document element not found") + SendWrapper::new( + document() + .document_element() + .expect("document element not found"), + ) } else { - el + SendWrapper::new(el) } }) }); let is_element_visible = use_element_visibility(observed_element); - let check_and_load = StoredValue::new(None::>); + let check_and_load = StoredValue::new(None::>); - check_and_load.set_value(Some(Rc::new({ + check_and_load.set_value(Some(Arc::new({ let measure = measure.clone(); move || { @@ -215,7 +218,7 @@ where #[derive(DefaultBuilder)] pub struct UseInfiniteScrollOptions { /// Callback when scrolling is happening. - on_scroll: Rc, + on_scroll: Arc, /// Options passed to the `addEventListener("scroll", ...)` call event_listener_options: UseEventListenerOptions, @@ -233,7 +236,7 @@ pub struct UseInfiniteScrollOptions { impl Default for UseInfiniteScrollOptions { fn default() -> Self { Self { - on_scroll: Rc::new(|_| {}), + on_scroll: Arc::new(|_| {}), event_listener_options: Default::default(), distance: 0.0, direction: Direction::Bottom, diff --git a/src/use_intersection_observer.rs b/src/use_intersection_observer.rs index 7e3bcea..5f24990 100644 --- a/src/use_intersection_observer.rs +++ b/src/use_intersection_observer.rs @@ -3,12 +3,14 @@ use cfg_if::cfg_if; use default_struct_builder::DefaultBuilder; use leptos::prelude::wrappers::read::Signal; use leptos::prelude::*; +use send_wrapper::SendWrapper; use std::marker::PhantomData; cfg_if! { if #[cfg(not(feature = "ssr"))] { use crate::{watch_with_options, WatchOptions}; - use std::cell::RefCell; - use std::rc::Rc; + // use std::cell::RefCell; + // use std::rc::Rc; + use std::sync::{Arc, Mutex}; use wasm_bindgen::prelude::*; }} @@ -116,14 +118,14 @@ where ) .into_js_value(); - let observer: Rc>> = - Rc::new(RefCell::new(None)); + let observer: Arc>>> = + Arc::new(Mutex::new(None)); let cleanup = { - let obsserver = Rc::clone(&observer); + let observer = Arc::clone(&observer); move || { - if let Some(o) = obsserver.take() { + if let Some(o) = observer.lock().unwrap().take() { o.disconnect(); } } @@ -173,11 +175,11 @@ where .expect("failed to create IntersectionObserver"); for target in targets.iter().flatten() { - let target: web_sys::Element = target.clone().into(); + let target: web_sys::Element = target.clone().take().into(); obs.observe(&target); } - observer.replace(Some(obs)); + *observer.lock().unwrap() = Some(SendWrapper::new(obs)); }, WatchOptions::default().immediate(immediate), ) diff --git a/src/use_intl_number_format.rs b/src/use_intl_number_format.rs index 9fe9aca..ca95fd0 100644 --- a/src/use_intl_number_format.rs +++ b/src/use_intl_number_format.rs @@ -6,6 +6,7 @@ use cfg_if::cfg_if; use default_struct_builder::DefaultBuilder; use leptos::prelude::wrappers::read::Signal; use leptos::prelude::*; +use send_wrapper::SendWrapper; use std::fmt::Display; use wasm_bindgen::{JsCast, JsValue}; @@ -167,7 +168,7 @@ pub fn use_intl_number_format(options: UseIntlNumberFormatOptions) -> UseIntlNum ); UseIntlNumberFormatReturn { - js_intl_number_format: number_format, + js_intl_number_format: SendWrapper::new(number_format), } }} } @@ -768,7 +769,7 @@ cfg_if! { if #[cfg(feature = "ssr")] { /// Return type of [`use_intl_number_format`]. pub struct UseIntlNumberFormatReturn { /// The instance of [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat). - pub js_intl_number_format: js_sys::Intl::NumberFormat, + pub js_intl_number_format: SendWrapper, } }} @@ -777,7 +778,7 @@ impl UseIntlNumberFormatReturn { /// See [`use_intl_number_format`] for more information. pub fn format(&self, number: impl Into>) -> Signal where - N: Clone + Display + 'static, + N: Clone + Display + Send + Sync + 'static, js_sys::Number: From, { let number = number.into(); @@ -854,8 +855,8 @@ impl UseIntlNumberFormatReturn { end: impl Into>, ) -> Signal where - NStart: Clone + Display + 'static, - NEnd: Clone + Display + 'static, + NStart: Clone + Display + Send + Sync + 'static, + NEnd: Clone + Display + Send + Sync + 'static, js_sys::Number: From, js_sys::Number: From, { diff --git a/src/use_media_query.rs b/src/use_media_query.rs index f3ad681..c5e3ffb 100644 --- a/src/use_media_query.rs +++ b/src/use_media_query.rs @@ -93,7 +93,10 @@ pub fn use_media_query(query: impl Into>) -> Signal { Effect::new(move |_| update()); - on_cleanup(cleanup); + on_cleanup({ + let cleanup = send_wrapper::SendWrapper::new(cleanup); + move || cleanup() + }); }} matches.into() diff --git a/src/use_mutation_observer.rs b/src/use_mutation_observer.rs index cd723b0..ceabaa4 100644 --- a/src/use_mutation_observer.rs +++ b/src/use_mutation_observer.rs @@ -2,6 +2,7 @@ use crate::core::ElementsMaybeSignal; use cfg_if::cfg_if; use default_struct_builder::DefaultBuilder; use leptos::prelude::wrappers::read::Signal; +use send_wrapper::SendWrapper; use wasm_bindgen::prelude::*; cfg_if! { if #[cfg(not(feature = "ssr"))] { @@ -124,7 +125,7 @@ where let stop_watch = { let cleanup = cleanup.clone(); - leptos::watch( + leptos::prelude::watch( move || targets.get(), move |targets, _, _| { cleanup(); @@ -135,7 +136,7 @@ where .expect("failed to create MutationObserver"); for target in targets.iter().flatten() { - let target: web_sys::Element = target.clone().into(); + let target: web_sys::Element = target.clone().take().into(); let _ = obs.observe_with_options(&target, &options.clone().into()); } @@ -151,7 +152,10 @@ where stop_watch(); }; - on_cleanup(stop.clone()); + on_cleanup({ + let stop = SendWrapper::new(stop.clone()); + move || stop() + }); UseMutationObserverReturn { is_supported, stop } } diff --git a/src/use_resize_observer.rs b/src/use_resize_observer.rs index 34bb0c2..c6d96e1 100644 --- a/src/use_resize_observer.rs +++ b/src/use_resize_observer.rs @@ -138,7 +138,7 @@ where .expect("failed to create ResizeObserver"); for target in targets.iter().flatten() { - let target: web_sys::Element = target.clone().into(); + let target: web_sys::Element = target.clone().take().into(); obs.observe_with_options(&target, &options.clone().into()); } observer.replace(Some(obs)); @@ -153,7 +153,10 @@ where stop_watch(); }; - on_cleanup(stop.clone()); + on_cleanup({ + let stop = send_wrapper::SendWrapper::new(stop.clone()); + move || stop() + }); UseResizeObserverReturn { is_supported, stop } } diff --git a/src/use_scroll.rs b/src/use_scroll.rs index d2ac791..b00dec2 100644 --- a/src/use_scroll.rs +++ b/src/use_scroll.rs @@ -221,6 +221,8 @@ where let set_y = |_| {}; let measure = || {}; } else { + use send_wrapper::SendWrapper; + let signal = element.into(); let behavior = options.behavior; @@ -372,7 +374,7 @@ where Signal::derive(move || { let element = signal.get(); - element.map(|element| element.into().unchecked_into::()) + element.map(|element| SendWrapper::new(element.into().unchecked_into::())) }) }; @@ -392,14 +394,14 @@ where let _ = use_event_listener_with_options::< _, - Signal>, + Signal>>, web_sys::EventTarget, _, >(target, ev::scroll, handler, options.event_listener_options); } else { let _ = use_event_listener_with_options::< _, - Signal>, + Signal>>, web_sys::EventTarget, _, >( @@ -412,7 +414,7 @@ where let _ = use_event_listener_with_options::< _, - Signal>, + Signal>>, web_sys::EventTarget, _, >( diff --git a/src/use_service_worker.rs b/src/use_service_worker.rs index 508b3d6..76958b1 100644 --- a/src/use_service_worker.rs +++ b/src/use_service_worker.rs @@ -256,7 +256,7 @@ fn create_action_update() -> Action< .and_then(|ok| ok.dyn_into::()) .map(SendWrapper::new) .map_err(SendWrapper::new), - Err(err) => Err(err), + Err(err) => Err(SendWrapper::new(err)), } } }, @@ -275,8 +275,10 @@ fn create_action_create_or_update_registration() -> Action< js_fut!(navigator.service_worker().register(script_url.as_str())) .await .and_then(|ok| ok.dyn_into::()) + .map(SendWrapper::new) + .map_err(SendWrapper::new) } else { - Err(JsValue::from_str("no navigator")) + Err(SendWrapper::new(JsValue::from_str("no navigator"))) } } }) diff --git a/src/use_sorted.rs b/src/use_sorted.rs index 02436ef..e837b4a 100644 --- a/src/use_sorted.rs +++ b/src/use_sorted.rs @@ -78,7 +78,7 @@ pub fn use_sorted(iterable: S) -> Signal where S: Into>, T: Ord, - I: DerefMut + Clone + PartialEq, + I: DerefMut + Clone + PartialEq + Send + Sync, { let iterable = iterable.into(); @@ -93,8 +93,8 @@ where pub fn use_sorted_by(iterable: S, cmp_fn: F) -> Signal where S: Into>, - I: DerefMut + Clone + PartialEq, - F: FnMut(&T, &T) -> Ordering + Clone + 'static, + I: DerefMut + Clone + PartialEq + Send + Sync, + F: FnMut(&T, &T) -> Ordering + Clone + Send + Sync + 'static, { let iterable = iterable.into(); @@ -109,9 +109,9 @@ where pub fn use_sorted_by_key(iterable: S, key_fn: F) -> Signal where S: Into>, - I: DerefMut + Clone + PartialEq, + I: DerefMut + Clone + PartialEq + Send + Sync, K: Ord, - F: FnMut(&T) -> K + Clone + 'static, + F: FnMut(&T) -> K + Clone + Send + Sync + 'static, { let iterable = iterable.into(); diff --git a/src/use_supported.rs b/src/use_supported.rs index b121e49..4da5ca3 100644 --- a/src/use_supported.rs +++ b/src/use_supported.rs @@ -20,7 +20,7 @@ use leptos::prelude::*; /// # view! { } /// # } /// ``` -pub fn use_supported(callback: impl Fn() -> bool + 'static) -> Signal { +pub fn use_supported(callback: impl Fn() -> bool + Send + Sync + 'static) -> Signal { #[cfg(feature = "ssr")] { let _ = callback; diff --git a/src/use_user_media.rs b/src/use_user_media.rs index 10d1c14..aba11dc 100644 --- a/src/use_user_media.rs +++ b/src/use_user_media.rs @@ -2,6 +2,7 @@ use crate::core::MaybeRwSignal; use cfg_if::cfg_if; use default_struct_builder::DefaultBuilder; use leptos::prelude::*; +use send_wrapper::SendWrapper; use wasm_bindgen::{JsCast, JsValue}; /// Reactive [`mediaDevices.getUserMedia`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia) streaming. @@ -59,7 +60,8 @@ pub fn use_user_media_with_options( let (enabled, set_enabled) = enabled.into_signal(); - let (stream, set_stream) = signal(None::>); + let (stream, set_stream) = + signal(None::, SendWrapper>>); let _start = move || async move { cfg_if! { if #[cfg(not(feature = "ssr"))] { @@ -67,7 +69,10 @@ pub fn use_user_media_with_options( return; } - let stream = create_media(video, audio).await; + let stream = create_media(video, audio) + .await + .map(SendWrapper::new) + .map_err(SendWrapper::new); set_stream.update(|s| *s = Some(stream)); } else { @@ -187,7 +192,7 @@ where /// Initially this is `None` until `start` resolved successfully. /// In case the stream couldn't be started, for example because the user didn't grant permission, /// this has the value `Some(Err(...))`. - pub stream: Signal>>, + pub stream: Signal, SendWrapper>>>, /// Starts the screen streaming. Triggers the ask for permission if not already granted. pub start: StartFn, diff --git a/src/use_web_notification.rs b/src/use_web_notification.rs index 5d183c3..afad4df 100644 --- a/src/use_web_notification.rs +++ b/src/use_web_notification.rs @@ -149,7 +149,7 @@ pub fn use_web_notification_with_options( notification_value.set_onerror(Some(on_error_closure.unchecked_ref())); notification_value.set_onshow(Some(on_show_closure.unchecked_ref())); - set_notification.set(Some(notification_value)); + set_notification.set(Some(SendWrapper::new(notification_value))); }); } }; diff --git a/src/use_websocket.rs b/src/use_websocket.rs index 2cf593f..776706d 100644 --- a/src/use_websocket.rs +++ b/src/use_websocket.rs @@ -237,7 +237,7 @@ pub fn use_websocket( impl Fn(&T) + Clone + 'static, > where - T: 'static, + T: Send + Sync + 'static, C: Encoder + Decoder, C: IsBinary>::Encoded>, C: HybridDecoder>::Encoded, Error = >::Error>, @@ -261,7 +261,7 @@ pub fn use_websocket_with_options( impl Fn(&T) + Clone + 'static, > where - T: 'static, + T: Send + Sync + 'static, C: Encoder + Decoder, C: IsBinary>::Encoded>, C: HybridDecoder>::Encoded, Error = >::Error>, @@ -358,7 +358,7 @@ where let on_open = Arc::clone(&on_open); let onopen_closure = Closure::wrap(Box::new(move |e: Event| { - if unmounted.get() { + if unmounted.load(std::sync::atomic::Ordering::Relaxed) { return; } @@ -387,7 +387,7 @@ where let on_error = Arc::clone(&on_error); let onmessage_closure = Closure::wrap(Box::new(move |e: MessageEvent| { - if unmounted.get() { + if unmounted.load(std::sync::atomic::Ordering::Relaxed) { return; } @@ -409,17 +409,18 @@ where on_message_raw(&txt); #[cfg(debug_assertions)] - SpecialNonReactiveZone::exit(prev); + drop(zone); match C::decode_str(&txt) { Ok(val) => { #[cfg(debug_assertions)] - let prev = SpecialNonReactiveZone::enter(); + let prev = + diagnostics::SpecialNonReactiveZone::enter(); on_message(&val); #[cfg(debug_assertions)] - drop(zone); + drop(prev); set_message.set(Some(val)); } @@ -445,12 +446,12 @@ where match C::decode_bin(array.as_slice()) { Ok(val) => { #[cfg(debug_assertions)] - let prev = SpecialNonReactiveZone::enter(); + let prev = diagnostics::SpecialNonReactiveZone::enter(); on_message(&val); #[cfg(debug_assertions)] - SpecialNonReactiveZone::exit(prev); + drop(prev); set_message.set(Some(val)); } @@ -472,7 +473,7 @@ where let on_error = Arc::clone(&on_error); let onerror_closure = Closure::wrap(Box::new(move |e: Event| { - if unmounted.get() { + if unmounted.load(std::sync::atomic::Ordering::Relaxed) { return; } @@ -501,7 +502,7 @@ where let on_close = Arc::clone(&on_close); let onclose_closure = Closure::wrap(Box::new(move |e: CloseEvent| { - if unmounted.get() { + if unmounted.load(std::sync::atomic::Ordering::Relaxed) { return; } @@ -524,7 +525,7 @@ where onclose_closure.forget(); } - ws_ref.set_value(Some(web_socket)); + ws_ref.set_value(Some(SendWrapper::new(web_socket))); })) }); } @@ -642,7 +643,7 @@ where on_open: Arc, /// `WebSocket` message callback for typed message decoded by codec. #[builder(skip)] - on_message: Arc, + on_message: Arc, /// `WebSocket` message callback for text. on_message_raw: Arc, /// `WebSocket` message callback for binary. @@ -669,7 +670,7 @@ impl UseWebSocketOptions { /// `WebSocket` error callback. pub fn on_error(self, handler: F) -> Self where - F: Fn(UseWebSocketError) + 'static, + F: Fn(UseWebSocketError) + Send + Sync + 'static, { Self { on_error: Arc::new(handler), @@ -680,7 +681,7 @@ impl UseWebSocketOptions { /// `WebSocket` message callback for typed message decoded by codec. pub fn on_message(self, handler: F) -> Self where - F: Fn(&T) + 'static, + F: Fn(&T) + Send + Sync + 'static, { Self { on_message: Arc::new(handler), @@ -710,7 +711,7 @@ impl Default for UseWebSocketOptions { #[derive(Clone)] pub struct UseWebSocketReturn where - T: 'static, + T: Send + Sync + 'static, OpenFn: Fn() + Clone + 'static, CloseFn: Fn() + Clone + 'static, SendFn: Fn(&T) + Clone + 'static, diff --git a/src/utils/use_derive_signal.rs b/src/utils/use_derive_signal.rs index 1e7e9c2..e715129 100644 --- a/src/utils/use_derive_signal.rs +++ b/src/utils/use_derive_signal.rs @@ -1,5 +1,5 @@ /// Macro to easily create helper functions that derive a signal using a piece of code. -/// +/// /// See [`is_ok`] or [`use_to_string`] as examples. #[macro_export] macro_rules! use_derive_signal { @@ -11,6 +11,7 @@ macro_rules! use_derive_signal { $(#[$outer])* pub fn $name(value: V) -> Signal<$return_type> where + $inner_signal_type $(< $( $inner_type_param ),+ >)?: Send + Sync, V: Into)?>> $(, $( $type_param $( : $first_bound $(+ $rest_bound)* )? ),+ )? { let value = value.into();