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::locator::{Locator, Selector};
15use super::Page;
16use crate::error::LocatorError;
17
18pub type LocatorHandlerFn = Arc<
20 dyn Fn() -> Pin<Box<dyn Future<Output = Result<(), LocatorError>> + Send>>
21 + Send
22 + Sync,
23>;
24
25#[derive(Debug, Clone)]
27#[derive(Default)]
28pub struct LocatorHandlerOptions {
29 pub no_wait_after: bool,
31 pub times: Option<u32>,
33}
34
35
36#[derive(Clone)]
38struct RegisteredHandler {
39 id: u64,
41 selector: Selector,
43 handler: LocatorHandlerFn,
45 options: LocatorHandlerOptions,
47 run_count: u32,
49}
50
51impl std::fmt::Debug for RegisteredHandler {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 f.debug_struct("RegisteredHandler")
54 .field("id", &self.id)
55 .field("selector", &self.selector)
56 .field("options", &self.options)
57 .field("run_count", &self.run_count)
58 .finish()
59 }
60}
61
62#[derive(Debug)]
64pub struct LocatorHandlerManager {
65 handlers: RwLock<Vec<RegisteredHandler>>,
67 next_id: std::sync::atomic::AtomicU64,
69}
70
71impl LocatorHandlerManager {
72 pub fn new() -> Self {
74 Self {
75 handlers: RwLock::new(Vec::new()),
76 next_id: std::sync::atomic::AtomicU64::new(1),
77 }
78 }
79
80 #[instrument(level = "debug", skip(self, handler), fields(selector = ?selector))]
85 pub async fn add_handler<F, Fut>(
86 &self,
87 selector: Selector,
88 handler: F,
89 options: LocatorHandlerOptions,
90 ) -> u64
91 where
92 F: Fn() -> Fut + Send + Sync + 'static,
93 Fut: Future<Output = Result<(), LocatorError>> + Send + 'static,
94 {
95 let id = self.next_id.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
96 let handler_fn: LocatorHandlerFn = Arc::new(move || Box::pin(handler()));
97
98 let registered = RegisteredHandler {
99 id,
100 selector,
101 handler: handler_fn,
102 options,
103 run_count: 0,
104 };
105
106 let mut handlers = self.handlers.write().await;
107 handlers.push(registered);
108 debug!(handler_id = id, "Locator handler registered");
109 id
110 }
111
112 #[instrument(level = "debug", skip(self))]
114 pub async fn remove_handler_by_id(&self, id: u64) {
115 let mut handlers = self.handlers.write().await;
116 let initial_len = handlers.len();
117 handlers.retain(|h| h.id != id);
118
119 if handlers.len() < initial_len {
120 debug!(handler_id = id, "Locator handler removed");
121 } else {
122 debug!(handler_id = id, "No matching locator handler found");
123 }
124 }
125
126 #[instrument(level = "debug", skip(self, page))]
130 pub async fn try_handle_blocking(&self, page: &Page) -> bool {
131 let handlers = self.handlers.read().await;
132
133 for handler in handlers.iter() {
134 let locator = Locator::new(page, handler.selector.clone());
136
137 if let Ok(is_visible) = locator.is_visible().await {
138 if is_visible {
139 let handler_id = handler.id;
140 debug!(handler_id = handler_id, "Handler selector matched, running handler");
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!(handler_id = handler_id, "Handler reached times limit, removing");
155 handlers.retain(|h| h.id != handler_id);
156 }
157 }
158 }
159
160 return true;
161 }
162
163 return false;
164 }
165 }
166 }
167
168 false
169 }
170}
171
172impl Default for LocatorHandlerManager {
173 fn default() -> Self {
174 Self::new()
175 }
176}
177
178#[derive(Debug, Clone, Copy)]
182pub struct LocatorHandlerHandle {
183 id: u64,
184}
185
186impl LocatorHandlerHandle {
187 pub(crate) fn new(id: u64) -> Self {
189 Self { id }
190 }
191
192 pub fn id(&self) -> u64 {
194 self.id
195 }
196}
197
198impl super::Page {
200 pub async fn add_locator_handler<F, Fut>(
220 &self,
221 locator: impl Into<super::Locator<'_>>,
222 handler: F,
223 ) -> LocatorHandlerHandle
224 where
225 F: Fn() -> Fut + Send + Sync + 'static,
226 Fut: std::future::Future<Output = Result<(), crate::error::LocatorError>> + Send + 'static,
227 {
228 let loc = locator.into();
229 let id = self
230 .locator_handler_manager
231 .add_handler(
232 loc.selector().clone(),
233 handler,
234 LocatorHandlerOptions::default(),
235 )
236 .await;
237 LocatorHandlerHandle::new(id)
238 }
239
240 pub async fn add_locator_handler_with_options<F, Fut>(
253 &self,
254 locator: impl Into<super::Locator<'_>>,
255 handler: F,
256 options: LocatorHandlerOptions,
257 ) -> LocatorHandlerHandle
258 where
259 F: Fn() -> Fut + Send + Sync + 'static,
260 Fut: std::future::Future<Output = Result<(), crate::error::LocatorError>> + Send + 'static,
261 {
262 let loc = locator.into();
263 let id = self
264 .locator_handler_manager
265 .add_handler(loc.selector().clone(), handler, options)
266 .await;
267 LocatorHandlerHandle::new(id)
268 }
269
270 pub async fn remove_locator_handler(&self, handle: LocatorHandlerHandle) {
272 self.locator_handler_manager.remove_handler_by_id(handle.id()).await;
273 }
274
275 pub(crate) fn locator_handler_manager(&self) -> &LocatorHandlerManager {
277 &self.locator_handler_manager
278 }
279}