use std::time::Duration;
use serde_json::json;
use tokio::time::Instant;
use crate::browser::interceptor::InterceptedRequest;
use crate::browser::listener::DataPacket;
use crate::browser::tab::{CookieParam, ListenStream, LoadMode, Tab};
use crate::{Error, Result};
pub struct Wait {
tab: Tab,
}
impl Wait {
pub(crate) fn new(tab: Tab) -> Self {
Self { tab }
}
fn timeout_or_default(&self, t: Option<Duration>) -> Duration {
t.unwrap_or_else(|| self.tab.core.timeout())
}
pub async fn secs(&self, secs: f64) {
tokio::time::sleep(Duration::from_secs_f64(secs.max(0.0))).await;
}
pub async fn doc_loaded(&self, timeout: Option<Duration>) -> Result<bool> {
let d = self.timeout_or_default(timeout);
self.tab.core.poll_ready_complete(d).await
}
pub async fn ele_loaded(&self, selector: &str, timeout: Option<Duration>) -> Result<bool> {
let deadline = Instant::now() + self.timeout_or_default(timeout);
loop {
if self.tab.find_once(selector).await?.is_some() {
return Ok(true);
}
if Instant::now() >= deadline {
return Ok(false);
}
tokio::time::sleep(Duration::from_millis(100)).await;
}
}
pub async fn ele_displayed(&self, selector: &str, timeout: Option<Duration>) -> Result<bool> {
let deadline = Instant::now() + self.timeout_or_default(timeout);
loop {
if let Some(el) = self.tab.find_once(selector).await?
&& el.is_displayed().await.unwrap_or(false)
{
return Ok(true);
}
if Instant::now() >= deadline {
return Ok(false);
}
tokio::time::sleep(Duration::from_millis(100)).await;
}
}
pub async fn ele_deleted(&self, selector: &str, timeout: Option<Duration>) -> Result<bool> {
let deadline = Instant::now() + self.timeout_or_default(timeout);
loop {
if self.tab.find_once(selector).await?.is_none() {
return Ok(true);
}
if Instant::now() >= deadline {
return Ok(false);
}
tokio::time::sleep(Duration::from_millis(100)).await;
}
}
pub async fn upload_paths_inputted(&self, timeout: Option<Duration>) -> Result<bool> {
self.tab.wait_upload_paths_inputted(timeout).await
}
pub async fn title_contains(&self, sub: &str, timeout: Option<Duration>) -> Result<bool> {
let deadline = Instant::now() + self.timeout_or_default(timeout);
loop {
if self.tab.title().await.unwrap_or_default().contains(sub) {
return Ok(true);
}
if Instant::now() >= deadline {
return Ok(false);
}
tokio::time::sleep(Duration::from_millis(100)).await;
}
}
pub async fn url_contains(&self, sub: &str, timeout: Option<Duration>) -> Result<bool> {
let deadline = Instant::now() + self.timeout_or_default(timeout);
loop {
if self.tab.url().await.unwrap_or_default().contains(sub) {
return Ok(true);
}
if Instant::now() >= deadline {
return Ok(false);
}
tokio::time::sleep(Duration::from_millis(100)).await;
}
}
pub async fn new_tab(&self, timeout: Option<Duration>) -> Result<Option<Tab>> {
let conn = self.tab.core.conn.clone();
let opener = self.tab.core.target_id.clone();
let ctx = self.tab.core.browser_context_id.clone();
let dl = self.tab.core.download_path.clone();
let dur = self.timeout_or_default(timeout);
let deadline = Instant::now() + dur;
let mut events = conn.subscribe();
loop {
let remain = deadline.saturating_duration_since(Instant::now());
if remain.is_zero() {
return Ok(None);
}
let ev = match tokio::time::timeout(remain, events.recv()).await {
Ok(Ok(ev)) => ev,
Ok(Err(tokio::sync::broadcast::error::RecvError::Lagged(_))) => continue,
Ok(Err(tokio::sync::broadcast::error::RecvError::Closed)) => return Ok(None),
Err(_) => return Ok(None),
};
if ev.method != "Browser.attachedToTarget" {
continue;
}
let ti = &ev.params["targetInfo"];
if ti["type"].as_str() != Some("page") {
continue;
}
let target_id = ti["targetId"].as_str().unwrap_or_default().to_string();
if target_id.is_empty() || target_id == opener {
continue;
}
if ti["browserContextId"].as_str() != Some(ctx.as_str()) {
continue;
}
let Some(session_id) = ev.params["sessionId"].as_str().map(str::to_string) else {
continue;
};
let tab = Tab::from_attached(conn, target_id, ctx, session_id, events, dl, dur).await?;
return Ok(Some(tab));
}
}
}
pub struct Scroll {
tab: Tab,
}
impl Scroll {
pub(crate) fn new(tab: Tab) -> Self {
Self { tab }
}
pub async fn to_top(&self) -> Result<()> {
self.run("window.scrollTo(window.scrollX, 0)").await
}
pub async fn to_bottom(&self) -> Result<()> {
self.run(
"window.scrollTo(window.scrollX, \
Math.max(document.documentElement.scrollHeight, \
document.body ? document.body.scrollHeight : 0))",
)
.await
}
pub async fn to_left(&self) -> Result<()> {
self.run("window.scrollTo(0, window.scrollY)").await
}
pub async fn to_right(&self) -> Result<()> {
self.run(
"window.scrollTo(Math.max(document.documentElement.scrollWidth, \
document.body ? document.body.scrollWidth : 0), window.scrollY)",
)
.await
}
pub async fn to_half(&self) -> Result<()> {
self.run(
"window.scrollTo(window.scrollX, \
Math.max(document.documentElement.scrollHeight, \
document.body ? document.body.scrollHeight : 0) / 2)",
)
.await
}
pub async fn to_location(&self, x: f64, y: f64) -> Result<()> {
self.run(&format!("window.scrollTo({x}, {y})")).await
}
pub async fn up(&self, pixel: f64) -> Result<()> {
self.tab.scroll_by(0.0, -pixel).await
}
pub async fn down(&self, pixel: f64) -> Result<()> {
self.tab.scroll_by(0.0, pixel).await
}
pub async fn left(&self, pixel: f64) -> Result<()> {
self.tab.scroll_by(-pixel, 0.0).await
}
pub async fn right(&self, pixel: f64) -> Result<()> {
self.tab.scroll_by(pixel, 0.0).await
}
async fn run(&self, js: &str) -> Result<()> {
self.tab.run_js(js).await?;
Ok(())
}
}
pub struct SetTab {
tab: Tab,
}
impl SetTab {
pub(crate) fn new(tab: Tab) -> Self {
Self { tab }
}
pub async fn cookies(&self, cookies: Vec<CookieParam>) -> Result<()> {
self.tab.set_cookies(cookies).await
}
pub fn timeout(&self, d: Duration) {
self.tab.core.set_timeout(d);
}
pub fn load_mode(&self, m: LoadMode) {
self.tab.core.set_load_mode(m);
}
pub async fn user_agent(&self, ua: &str) -> Result<()> {
self.tab
.core
.conn
.send(
"Browser.setUserAgentOverride",
json!({
"browserContextId": self.tab.core.browser_context_id,
"userAgent": ua,
}),
None,
)
.await?;
Ok(())
}
pub async fn upload_files(&self, paths: &[&str]) -> Result<()> {
self.tab.set_upload_files(paths).await
}
pub async fn local_storage_set(&self, key: &str, value: &str) -> Result<()> {
self.storage_set("localStorage", key, value).await
}
pub async fn local_storage_get(&self, key: &str) -> Result<Option<String>> {
self.storage_get("localStorage", key).await
}
pub async fn local_storage_remove(&self, key: &str) -> Result<()> {
self.storage_remove("localStorage", key).await
}
pub async fn local_storage_clear(&self) -> Result<()> {
self.tab.run_js("localStorage.clear()").await?;
Ok(())
}
pub async fn session_storage_set(&self, key: &str, value: &str) -> Result<()> {
self.storage_set("sessionStorage", key, value).await
}
pub async fn session_storage_get(&self, key: &str) -> Result<Option<String>> {
self.storage_get("sessionStorage", key).await
}
pub async fn session_storage_remove(&self, key: &str) -> Result<()> {
self.storage_remove("sessionStorage", key).await
}
pub async fn session_storage_clear(&self) -> Result<()> {
self.tab.run_js("sessionStorage.clear()").await?;
Ok(())
}
async fn storage_set(&self, store: &str, key: &str, value: &str) -> Result<()> {
let js = format!("{store}.setItem({}, {})", js_str(key), js_str(value));
self.tab.run_js(&js).await?;
Ok(())
}
async fn storage_get(&self, store: &str, key: &str) -> Result<Option<String>> {
let js = format!("{store}.getItem({})", js_str(key));
Ok(self.tab.run_js(&js).await?.as_str().map(str::to_string))
}
async fn storage_remove(&self, store: &str, key: &str) -> Result<()> {
let js = format!("{store}.removeItem({})", js_str(key));
self.tab.run_js(&js).await?;
Ok(())
}
pub fn window(&self) -> Window {
Window::new(self.tab.clone())
}
}
fn js_str(s: &str) -> String {
serde_json::to_string(s).unwrap_or_else(|_| "\"\"".into())
}
pub struct Window {
tab: Tab,
}
impl Window {
pub(crate) fn new(tab: Tab) -> Self {
Self { tab }
}
pub async fn size(&self, width: u32, height: u32) -> Result<()> {
self.tab.core.set_viewport_size(width, height).await
}
pub async fn viewport(&self, width: u32, height: u32) -> Result<()> {
self.tab.core.set_viewport_size(width, height).await
}
pub async fn max(&self) -> Result<()> {
let v = self
.tab
.run_js("[screen.availWidth, screen.availHeight]")
.await?;
let w = v.get(0).and_then(|x| x.as_f64()).unwrap_or(1280.0) as u32;
let h = v.get(1).and_then(|x| x.as_f64()).unwrap_or(800.0) as u32;
self.tab.core.set_viewport_size(w, h).await
}
}
pub struct Intercept {
tab: Tab,
}
impl Intercept {
pub(crate) fn new(tab: Tab) -> Self {
Self { tab }
}
pub async fn start(&self, targets: &[&str]) -> Result<()> {
self.tab.intercept_start(targets).await
}
pub async fn start_xhr(&self, targets: &[&str]) -> Result<()> {
self.tab.intercept_xhr(targets).await
}
pub async fn is_intercepting(&self) -> bool {
self.tab.core.interceptor.lock().await.is_some()
}
pub async fn next(&self) -> Result<InterceptedRequest> {
self.tab.intercept_next().await
}
pub async fn next_timeout(&self, timeout: Duration) -> Result<Option<InterceptedRequest>> {
self.tab.intercept_next_timeout(timeout).await
}
pub async fn stop(&self) -> Result<()> {
self.tab.intercept_stop().await
}
}
pub struct Listen {
tab: Tab,
}
impl Listen {
pub(crate) fn new(tab: Tab) -> Self {
Self { tab }
}
pub async fn start(&self, targets: &[&str]) -> Result<()> {
self.tab.listen_start(targets).await
}
pub async fn is_listening(&self) -> bool {
*self.tab.core.listen_active.lock().await
}
pub async fn wait(&self) -> Result<DataPacket> {
self.tab.listen_wait().await
}
pub async fn wait_timeout(&self, timeout: Duration) -> Result<Option<DataPacket>> {
match self.tab.listen_wait_timeout(timeout).await {
Ok(p) => Ok(Some(p)),
Err(Error::Timeout(_)) => Ok(None),
Err(e) => Err(e),
}
}
pub async fn next(&self) -> Result<Option<DataPacket>> {
self.tab.listen_next().await
}
pub async fn wait_count(
&self,
count: usize,
timeout: Option<Duration>,
) -> Result<Vec<DataPacket>> {
let total = timeout.unwrap_or_else(|| self.tab.core.timeout());
let deadline = Instant::now() + total;
let mut out = Vec::with_capacity(count);
while out.len() < count {
let remain = deadline.saturating_duration_since(Instant::now());
if remain.is_zero() {
break;
}
match self.tab.listen_wait_timeout(remain).await {
Ok(p) => out.push(p),
Err(Error::Timeout(_)) => break,
Err(e) => return Err(e),
}
}
Ok(out)
}
pub async fn steps(&self) -> Result<ListenStream> {
self.tab.listen_stream().await
}
pub async fn stop(&self) -> Result<()> {
self.tab.listen_stop().await
}
}