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