1#![cfg(target_arch = "wasm32")]
8#![deny(missing_docs)]
9
10use std::cell::RefCell;
11
12use gloo_events::EventListener;
13use swr_core::{Instant, Runtime, RuntimeFuture, SwrClient, SwrEvent};
14
15#[derive(Clone, Copy, Debug, Default)]
20pub struct WebRuntime;
21
22impl WebRuntime {
23 pub fn new() -> Self {
25 Self
26 }
27}
28
29impl Runtime for WebRuntime {
30 fn now(&self) -> Instant {
31 Instant::now()
34 }
35
36 fn spawn(&self, fut: RuntimeFuture) {
37 wasm_bindgen_futures::spawn_local(fut);
38 }
39
40 fn sleep_until(&self, at: Instant) -> RuntimeFuture {
41 Box::pin(async move {
42 let delay = at.saturating_duration_since(Instant::now());
44 gloo_timers::future::sleep(delay).await;
45 })
46 }
47}
48
49#[must_use = "dropping the WebEventSource detaches the client from browser events"]
58pub struct WebEventSource {
59 id: u64,
60}
61
62impl WebEventSource {
63 pub fn attach(client: &SwrClient) -> Self {
70 REGISTRY.with(|registry| registry.borrow_mut().attach(client.clone()))
71 }
72}
73
74impl Drop for WebEventSource {
75 fn drop(&mut self) {
76 REGISTRY.with(|registry| registry.borrow_mut().detach(self.id));
77 }
78}
79
80thread_local! {
81 static REGISTRY: RefCell<Registry> = RefCell::new(Registry::default());
82}
83
84#[derive(Default)]
85struct Registry {
86 next_id: u64,
87 clients: Vec<(u64, SwrClient)>,
88 listeners: Option<Vec<EventListener>>,
90}
91
92impl Registry {
93 fn attach(&mut self, client: SwrClient) -> WebEventSource {
94 let id = self.next_id;
95 self.next_id += 1;
96 self.clients.push((id, client));
97 if self.listeners.is_none() {
98 self.listeners = Some(register_dom_listeners());
99 }
100 WebEventSource { id }
101 }
102
103 fn detach(&mut self, id: u64) {
104 self.clients.retain(|(client_id, _)| *client_id != id);
105 if self.clients.is_empty() {
106 self.listeners = None;
107 }
108 }
109}
110
111fn register_dom_listeners() -> Vec<EventListener> {
112 let window = web_sys::window().expect("browser environment: window missing");
113 let document = window
114 .document()
115 .expect("browser environment: document missing");
116 vec![
117 EventListener::new(&window, "focus", |_event| broadcast_all(SwrEvent::Focus)),
118 EventListener::new(&window, "online", |_event| broadcast_all(SwrEvent::Online)),
119 EventListener::new(&document, "visibilitychange", |_event| {
120 if document_visible() {
122 broadcast_all(SwrEvent::Focus);
123 }
124 }),
125 ]
126}
127
128fn document_visible() -> bool {
129 web_sys::window()
130 .and_then(|window| window.document())
131 .is_some_and(|document| document.visibility_state() == web_sys::VisibilityState::Visible)
132}
133
134fn broadcast_all(ev: SwrEvent) {
135 let clients: Vec<SwrClient> = REGISTRY.with(|registry| {
138 registry
139 .borrow()
140 .clients
141 .iter()
142 .map(|(_, client)| client.clone())
143 .collect()
144 });
145 for client in clients {
146 client.broadcast(ev);
147 }
148}