use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::Mutex;
use crate::error::Result;
use crate::locator::Locator;
pub type LocatorHandlerFn = Arc<dyn Fn(Locator) -> Pin<Box<dyn Future<Output = Result<()>> + Send>> + Send + Sync>;
struct LocatorHandlerEntry {
uid: u64,
selector: String,
callback: LocatorHandlerFn,
times: Option<u32>,
no_wait_after: bool,
}
#[derive(Default)]
pub(crate) struct LocatorHandlerRegistry {
inner: Mutex<RegistryState>,
}
#[derive(Default)]
struct RegistryState {
handlers: Vec<LocatorHandlerEntry>,
next_uid: u64,
running: bool,
}
impl LocatorHandlerRegistry {
pub(crate) fn register(
&self,
selector: String,
callback: LocatorHandlerFn,
times: Option<u32>,
no_wait_after: bool,
) -> Option<u64> {
if times == Some(0) {
return None;
}
let mut state = self.inner.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
state.next_uid += 1;
let uid = state.next_uid;
state.handlers.push(LocatorHandlerEntry {
uid,
selector,
callback,
times,
no_wait_after,
});
Some(uid)
}
pub(crate) fn remove_by_selector(&self, selector: &str) {
let mut state = self.inner.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
state.handlers.retain(|h| h.selector != selector);
}
fn is_empty(&self) -> bool {
self
.inner
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.handlers
.is_empty()
}
}
pub(crate) async fn perform_checkpoint(page: &Arc<crate::page::Page>) {
let registry = page.locator_handlers();
if registry.is_empty() {
return;
}
{
let mut state = registry.inner.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
if state.running {
return;
}
state.running = true;
}
let snapshot: Vec<(u64, String, LocatorHandlerFn, bool)> = {
let state = registry.inner.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
state
.handlers
.iter()
.map(|h| (h.uid, h.selector.clone(), Arc::clone(&h.callback), h.no_wait_after))
.collect()
};
for (uid, selector, callback, no_wait_after) in snapshot {
let locator = page.locator(&selector, None);
let visible = locator.is_visible().await.unwrap_or(false);
if !visible {
continue;
}
let should_run = {
let mut state = registry.inner.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
match state.handlers.iter_mut().find(|h| h.uid == uid) {
Some(entry) => match entry.times {
Some(0) => false,
Some(ref mut n) => {
*n -= 1;
true
},
None => true,
},
None => false,
}
};
if !should_run {
continue;
}
let _ = callback(locator.clone()).await;
let remove = {
let state = registry.inner.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
state
.handlers
.iter()
.find(|h| h.uid == uid)
.is_some_and(|h| h.times == Some(0))
};
if remove {
let mut state = registry.inner.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
state.handlers.retain(|h| h.uid != uid);
}
if !no_wait_after {
let _ = wait_hidden(&locator).await;
}
}
let mut state = registry.inner.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
state.running = false;
}
async fn wait_hidden(locator: &Locator) -> Result<()> {
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(30);
loop {
if !locator.is_visible().await.unwrap_or(false) {
return Ok(());
}
if std::time::Instant::now() >= deadline {
return Ok(());
}
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
}
}