viewpoint_core/page/locator_handler/
mod.rs1use std::future::Future;
8use std::pin::Pin;
9use std::sync::Arc;
10
11use tokio::sync::RwLock;
12use tracing::{debug, instrument, warn};
13
14use super::Page;
15use super::locator::{Locator, Selector};
16use crate::error::LocatorError;
17
18pub type LocatorHandlerFn =
20 Arc<dyn Fn() -> Pin<Box<dyn Future<Output = Result<(), LocatorError>> + Send>> + Send + Sync>;
21
22#[derive(Debug, Clone, Default)]
24pub struct LocatorHandlerOptions {
25 pub no_wait_after: bool,
27 pub times: Option<u32>,
29}
30
31#[derive(Clone)]
33struct RegisteredHandler {
34 id: u64,
36 selector: Selector,
38 handler: LocatorHandlerFn,
40 options: LocatorHandlerOptions,
42 run_count: u32,
44}
45
46impl std::fmt::Debug for RegisteredHandler {
47 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 f.debug_struct("RegisteredHandler")
49 .field("id", &self.id)
50 .field("selector", &self.selector)
51 .field("options", &self.options)
52 .field("run_count", &self.run_count)
53 .finish()
54 }
55}
56
57#[derive(Debug)]
59pub struct LocatorHandlerManager {
60 handlers: RwLock<Vec<RegisteredHandler>>,
62 next_id: std::sync::atomic::AtomicU64,
64}
65
66impl LocatorHandlerManager {
67 pub fn new() -> Self {
69 Self {
70 handlers: RwLock::new(Vec::new()),
71 next_id: std::sync::atomic::AtomicU64::new(1),
72 }
73 }
74
75 #[instrument(level = "debug", skip(self, handler), fields(selector = ?selector))]
80 pub async fn add_handler<F, Fut>(
81 &self,
82 selector: Selector,
83 handler: F,
84 options: LocatorHandlerOptions,
85 ) -> u64
86 where
87 F: Fn() -> Fut + Send + Sync + 'static,
88 Fut: Future<Output = Result<(), LocatorError>> + Send + 'static,
89 {
90 let id = self
91 .next_id
92 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
93 let handler_fn: LocatorHandlerFn = Arc::new(move || Box::pin(handler()));
94
95 let registered = RegisteredHandler {
96 id,
97 selector,
98 handler: handler_fn,
99 options,
100 run_count: 0,
101 };
102
103 let mut handlers = self.handlers.write().await;
104 handlers.push(registered);
105 debug!(handler_id = id, "Locator handler registered");
106 id
107 }
108
109 #[instrument(level = "debug", skip(self))]
111 pub async fn remove_handler_by_id(&self, id: u64) {
112 let mut handlers = self.handlers.write().await;
113 let initial_len = handlers.len();
114 handlers.retain(|h| h.id != id);
115
116 if handlers.len() < initial_len {
117 debug!(handler_id = id, "Locator handler removed");
118 } else {
119 debug!(handler_id = id, "No matching locator handler found");
120 }
121 }
122
123 #[instrument(level = "debug", skip(self, page))]
127 pub async fn try_handle_blocking(&self, page: &Page) -> bool {
128 let handlers = self.handlers.read().await;
129
130 for handler in handlers.iter() {
131 let locator = Locator::new(page, handler.selector.clone());
133
134 if let Ok(is_visible) = locator.is_visible().await {
135 if is_visible {
136 let handler_id = handler.id;
137 debug!(
138 handler_id = handler_id,
139 "Handler selector matched, running handler"
140 );
141 let handler_fn = handler.handler.clone();
142 drop(handlers); if let Err(e) = handler_fn().await {
145 warn!(handler_id = handler_id, "Locator handler failed: {}", e);
146 } else {
147 let mut handlers = self.handlers.write().await;
149 if let Some(handler) = handlers.iter_mut().find(|h| h.id == handler_id) {
150 handler.run_count += 1;
151
152 if let Some(times) = handler.options.times {
153 if handler.run_count >= times {
154 debug!(
155 handler_id = handler_id,
156 "Handler reached times limit, removing"
157 );
158 handlers.retain(|h| h.id != handler_id);
159 }
160 }
161 }
162
163 return true;
164 }
165
166 return false;
167 }
168 }
169 }
170
171 false
172 }
173}
174
175impl Default for LocatorHandlerManager {
176 fn default() -> Self {
177 Self::new()
178 }
179}
180
181#[derive(Debug, Clone, Copy)]
185pub struct LocatorHandlerHandle {
186 id: u64,
187}
188
189impl LocatorHandlerHandle {
190 pub(crate) fn new(id: u64) -> Self {
192 Self { id }
193 }
194
195 pub fn id(&self) -> u64 {
197 self.id
198 }
199}
200
201impl super::Page {
203 pub async fn add_locator_handler<F, Fut>(
231 &self,
232 locator: impl Into<super::Locator<'_>>,
233 handler: F,
234 ) -> LocatorHandlerHandle
235 where
236 F: Fn() -> Fut + Send + Sync + 'static,
237 Fut: std::future::Future<Output = Result<(), crate::error::LocatorError>> + Send + 'static,
238 {
239 let loc = locator.into();
240 let id = self
241 .locator_handler_manager
242 .add_handler(
243 loc.selector().clone(),
244 handler,
245 LocatorHandlerOptions::default(),
246 )
247 .await;
248 LocatorHandlerHandle::new(id)
249 }
250
251 pub async fn add_locator_handler_with_options<F, Fut>(
274 &self,
275 locator: impl Into<super::Locator<'_>>,
276 handler: F,
277 options: LocatorHandlerOptions,
278 ) -> LocatorHandlerHandle
279 where
280 F: Fn() -> Fut + Send + Sync + 'static,
281 Fut: std::future::Future<Output = Result<(), crate::error::LocatorError>> + Send + 'static,
282 {
283 let loc = locator.into();
284 let id = self
285 .locator_handler_manager
286 .add_handler(loc.selector().clone(), handler, options)
287 .await;
288 LocatorHandlerHandle::new(id)
289 }
290
291 pub async fn remove_locator_handler(&self, handle: LocatorHandlerHandle) {
293 self.locator_handler_manager
294 .remove_handler_by_id(handle.id())
295 .await;
296 }
297
298 pub(crate) fn locator_handler_manager(&self) -> &LocatorHandlerManager {
300 &self.locator_handler_manager
301 }
302}