viewpoint_core/context/events/
mod.rs

1//! Event system infrastructure for browser context.
2//!
3//! This module provides the event handling infrastructure for `BrowserContext`,
4//! including event handlers, handler IDs, and event emitters.
5
6use std::collections::HashMap;
7use std::future::Future;
8use std::pin::Pin;
9use std::sync::Arc;
10use std::sync::atomic::{AtomicU64, Ordering};
11
12use tokio::sync::RwLock;
13
14use crate::page::Page;
15
16/// A unique identifier for an event handler.
17///
18/// This ID is returned when registering an event handler and can be used
19/// to remove the handler later.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
21pub struct HandlerId(u64);
22
23impl HandlerId {
24    /// Generate a new unique handler ID.
25    pub(crate) fn new() -> Self {
26        static COUNTER: AtomicU64 = AtomicU64::new(1);
27        Self(COUNTER.fetch_add(1, Ordering::SeqCst))
28    }
29}
30
31/// Type alias for the page event handler function.
32pub type PageEventHandler =
33    Box<dyn Fn(Page) -> Pin<Box<dyn Future<Output = ()> + Send>> + Send + Sync>;
34
35/// Type alias for the page activated event handler function.
36///
37/// This handler is called when a page becomes the active/foreground tab,
38/// including when the user clicks on a tab in the browser UI.
39pub type PageActivatedEventHandler =
40    Box<dyn Fn(Page) -> Pin<Box<dyn Future<Output = ()> + Send>> + Send + Sync>;
41
42/// Type alias for the close event handler function.
43pub type CloseEventHandler =
44    Box<dyn Fn() -> Pin<Box<dyn Future<Output = ()> + Send>> + Send + Sync>;
45
46/// Event emitter for managing typed event handlers.
47///
48/// This provides a thread-safe way to register, invoke, and remove
49/// event handlers of a specific type.
50pub struct EventEmitter<H> {
51    handlers: RwLock<HashMap<HandlerId, H>>,
52}
53
54impl<H> Default for EventEmitter<H> {
55    fn default() -> Self {
56        Self::new()
57    }
58}
59
60impl<H> EventEmitter<H> {
61    /// Create a new empty event emitter.
62    pub fn new() -> Self {
63        Self {
64            handlers: RwLock::new(HashMap::new()),
65        }
66    }
67
68    /// Add a handler and return its ID.
69    pub async fn add(&self, handler: H) -> HandlerId {
70        let id = HandlerId::new();
71        let mut handlers = self.handlers.write().await;
72        handlers.insert(id, handler);
73        id
74    }
75
76    /// Remove a handler by its ID.
77    ///
78    /// Returns `true` if a handler was removed, `false` if the ID was not found.
79    pub async fn remove(&self, id: HandlerId) -> bool {
80        let mut handlers = self.handlers.write().await;
81        handlers.remove(&id).is_some()
82    }
83
84    /// Remove all handlers.
85    pub async fn clear(&self) {
86        let mut handlers = self.handlers.write().await;
87        handlers.clear();
88    }
89
90    /// Check if there are any handlers registered.
91    pub async fn is_empty(&self) -> bool {
92        let handlers = self.handlers.read().await;
93        handlers.is_empty()
94    }
95
96    /// Get the number of registered handlers.
97    pub async fn len(&self) -> usize {
98        let handlers = self.handlers.read().await;
99        handlers.len()
100    }
101}
102
103impl EventEmitter<PageEventHandler> {
104    /// Emit an event to all page handlers.
105    ///
106    /// Each handler is called with a clone of the page.
107    pub async fn emit_page(&self, page: Page) {
108        let handlers = self.handlers.read().await;
109        for handler in handlers.values() {
110            // Clone the page for each handler
111            // Note: We need to create a new page reference for each handler
112            handler(page.clone_internal()).await;
113        }
114    }
115}
116
117impl EventEmitter<PageActivatedEventHandler> {
118    /// Emit a page activated event to all handlers.
119    ///
120    /// Each handler is called with a clone of the page.
121    pub async fn emit_page_activated(&self, page: Page) {
122        let handlers = self.handlers.read().await;
123        for handler in handlers.values() {
124            // Clone the page for each handler
125            handler(page.clone_internal()).await;
126        }
127    }
128}
129
130impl EventEmitter<CloseEventHandler> {
131    /// Emit a close event to all handlers.
132    pub async fn emit(&self) {
133        let handlers = self.handlers.read().await;
134        for handler in handlers.values() {
135            handler().await;
136        }
137    }
138}
139
140/// Context event manager that handles all context-level events.
141#[derive(Default)]
142pub struct ContextEventManager {
143    /// Handlers for 'page' events (new page created).
144    page_handlers: EventEmitter<PageEventHandler>,
145    /// Handlers for 'page_activated' events (page became active/foreground).
146    page_activated_handlers: EventEmitter<PageActivatedEventHandler>,
147    /// Handlers for 'close' events (context closing).
148    close_handlers: EventEmitter<CloseEventHandler>,
149}
150
151impl ContextEventManager {
152    /// Create a new context event manager.
153    pub fn new() -> Self {
154        Self {
155            page_handlers: EventEmitter::new(),
156            page_activated_handlers: EventEmitter::new(),
157            close_handlers: EventEmitter::new(),
158        }
159    }
160
161    /// Register a handler for new page events.
162    ///
163    /// The handler will be called whenever a new page is created in the context.
164    /// Returns a handler ID that can be used to remove the handler.
165    pub async fn on_page<F, Fut>(&self, handler: F) -> HandlerId
166    where
167        F: Fn(Page) -> Fut + Send + Sync + 'static,
168        Fut: Future<Output = ()> + Send + 'static,
169    {
170        let boxed_handler: PageEventHandler = Box::new(move |page| Box::pin(handler(page)));
171        self.page_handlers.add(boxed_handler).await
172    }
173
174    /// Remove a page event handler by its ID.
175    ///
176    /// Returns `true` if a handler was removed.
177    pub async fn off_page(&self, id: HandlerId) -> bool {
178        self.page_handlers.remove(id).await
179    }
180
181    /// Emit a page event to all registered handlers.
182    pub async fn emit_page(&self, page: Page) {
183        self.page_handlers.emit_page(page).await;
184    }
185
186    /// Register a handler for page activated events.
187    ///
188    /// The handler will be called when a page becomes the active/foreground tab,
189    /// including when the user clicks on a tab in the browser UI.
190    /// Returns a handler ID that can be used to remove the handler.
191    pub async fn on_page_activated<F, Fut>(&self, handler: F) -> HandlerId
192    where
193        F: Fn(Page) -> Fut + Send + Sync + 'static,
194        Fut: Future<Output = ()> + Send + 'static,
195    {
196        let boxed_handler: PageActivatedEventHandler =
197            Box::new(move |page| Box::pin(handler(page)));
198        self.page_activated_handlers.add(boxed_handler).await
199    }
200
201    /// Remove a page activated event handler by its ID.
202    ///
203    /// Returns `true` if a handler was removed.
204    pub async fn off_page_activated(&self, id: HandlerId) -> bool {
205        self.page_activated_handlers.remove(id).await
206    }
207
208    /// Emit a page activated event to all registered handlers.
209    pub async fn emit_page_activated(&self, page: Page) {
210        self.page_activated_handlers.emit_page_activated(page).await;
211    }
212
213    /// Register a handler for context close events.
214    ///
215    /// The handler will be called when the context is about to close.
216    /// Returns a handler ID that can be used to remove the handler.
217    pub async fn on_close<F, Fut>(&self, handler: F) -> HandlerId
218    where
219        F: Fn() -> Fut + Send + Sync + 'static,
220        Fut: Future<Output = ()> + Send + 'static,
221    {
222        let boxed_handler: CloseEventHandler = Box::new(move || Box::pin(handler()));
223        self.close_handlers.add(boxed_handler).await
224    }
225
226    /// Remove a close event handler by its ID.
227    ///
228    /// Returns `true` if a handler was removed.
229    pub async fn off_close(&self, id: HandlerId) -> bool {
230        self.close_handlers.remove(id).await
231    }
232
233    /// Emit a close event to all registered handlers.
234    pub async fn emit_close(&self) {
235        self.close_handlers.emit().await;
236    }
237
238    /// Clear all event handlers.
239    pub async fn clear(&self) {
240        self.page_handlers.clear().await;
241        self.page_activated_handlers.clear().await;
242        self.close_handlers.clear().await;
243    }
244}
245
246/// Builder for waiting for a page during an action.
247///
248/// This is used with `context.wait_for_page()` to wait for a new page
249/// to be created during the execution of an action.
250pub struct WaitForPageBuilder<'a, F, Fut>
251where
252    F: FnOnce() -> Fut,
253    Fut: Future<Output = Result<(), crate::error::ContextError>>,
254{
255    event_manager: &'a Arc<ContextEventManager>,
256    action: Option<F>,
257}
258
259impl<'a, F, Fut> WaitForPageBuilder<'a, F, Fut>
260where
261    F: FnOnce() -> Fut,
262    Fut: Future<Output = Result<(), crate::error::ContextError>>,
263{
264    /// Create a new builder.
265    pub(crate) fn new(event_manager: &'a Arc<ContextEventManager>, action: F) -> Self {
266        Self {
267            event_manager,
268            action: Some(action),
269        }
270    }
271
272    /// Execute the action and wait for a new page.
273    ///
274    /// Returns the new page that was created during the action.
275    ///
276    /// # Errors
277    ///
278    /// Returns an error if the action fails or times out waiting for a new page.
279    ///
280    /// # Panics
281    ///
282    /// Panics if the action has already been consumed.
283    pub async fn wait(mut self) -> Result<Page, crate::error::ContextError> {
284        use tokio::sync::oneshot;
285
286        // Create a channel to receive the new page
287        let (tx, rx) = oneshot::channel::<Page>();
288        let tx = Arc::new(tokio::sync::Mutex::new(Some(tx)));
289
290        // Register a temporary handler to capture the new page
291        let tx_clone = tx.clone();
292        let handler_id = self
293            .event_manager
294            .on_page(move |page| {
295                let tx = tx_clone.clone();
296                async move {
297                    let mut guard = tx.lock().await;
298                    if let Some(sender) = guard.take() {
299                        let _ = sender.send(page);
300                    }
301                }
302            })
303            .await;
304
305        // Execute the action
306        let action = self.action.take().expect("action already consumed");
307        let action_result = action().await;
308
309        // Wait for the page or handle action error
310        let result = match action_result {
311            Ok(()) => {
312                // Wait for the page with timeout
313                match tokio::time::timeout(std::time::Duration::from_secs(30), rx).await {
314                    Ok(Ok(page)) => Ok(page),
315                    Ok(Err(_)) => Err(crate::error::ContextError::Internal(
316                        "Page channel closed unexpectedly".to_string(),
317                    )),
318                    Err(_) => Err(crate::error::ContextError::Timeout {
319                        operation: "wait_for_page".to_string(),
320                        duration: std::time::Duration::from_secs(30),
321                    }),
322                }
323            }
324            Err(e) => Err(e),
325        };
326
327        // Clean up the handler
328        self.event_manager.off_page(handler_id).await;
329
330        result
331    }
332}
333
334#[cfg(test)]
335mod tests;