use std::sync::Arc;
use std::time::Duration;
use serde_json::{Value, json};
use tokio::time::{Instant, sleep};
use crate::Result;
use crate::cdp::core::CdpCore;
use crate::cdp::element::ChromiumElement;
use crate::cdp::tab::doc_query_expr;
use crate::cdp::types::CookieParam;
pub struct ChromiumScroll {
core: Arc<CdpCore>,
}
impl ChromiumScroll {
pub(crate) fn new(core: Arc<CdpCore>) -> Self {
Self { core }
}
async fn js(&self, expr: &str) -> Result<()> {
self.core.eval_value(expr).await?;
Ok(())
}
pub async fn to_top(&self) -> Result<()> {
self.js("window.scrollTo(0,0)").await
}
pub async fn to_bottom(&self) -> Result<()> {
self.js("window.scrollTo(0, document.documentElement.scrollHeight)")
.await
}
pub async fn to_left(&self) -> Result<()> {
self.js("window.scrollTo(0, window.scrollY)").await
}
pub async fn to_right(&self) -> Result<()> {
self.js("window.scrollTo(document.documentElement.scrollWidth, window.scrollY)")
.await
}
pub async fn by(&self, x: f64, y: f64) -> Result<()> {
self.js(&format!("window.scrollBy({x},{y})")).await
}
pub async fn to_location(&self, x: f64, y: f64) -> Result<()> {
self.js(&format!("window.scrollTo({x},{y})")).await
}
pub async fn to_see(&self, ele: &ChromiumElement) -> Result<()> {
ele.scroll_into_view().await
}
}
pub struct ChromiumSetTab {
core: Arc<CdpCore>,
}
impl ChromiumSetTab {
pub(crate) fn new(core: Arc<CdpCore>) -> Self {
Self { core }
}
pub fn timeout(&self, d: Duration) -> &Self {
self.core.set_timeout(d);
self
}
pub async fn user_agent(&self, ua: &str) -> Result<()> {
self.core
.send("Emulation.setUserAgentOverride", json!({ "userAgent": ua }))
.await?;
Ok(())
}
pub async fn cookies(&self, cookies: Vec<CookieParam>) -> Result<()> {
let arr: Vec<Value> = cookies.iter().map(cookie_param_json).collect();
self.core
.send("Storage.setCookies", json!({ "cookies": arr }))
.await?;
Ok(())
}
pub fn window(&self) -> ChromiumWindow {
ChromiumWindow::new(self.core.clone())
}
}
pub struct ChromiumWindow {
core: Arc<CdpCore>,
}
impl ChromiumWindow {
pub(crate) fn new(core: Arc<CdpCore>) -> Self {
Self { core }
}
async fn window_id(&self) -> Result<i64> {
let r = self
.core
.send(
"Browser.getWindowForTarget",
json!({ "targetId": self.core.target_id }),
)
.await?;
Ok(r["windowId"].as_i64().unwrap_or(0))
}
pub async fn size(&self, width: u32, height: u32) -> Result<()> {
let id = self.window_id().await?;
self.core
.send(
"Browser.setWindowBounds",
json!({ "windowId": id, "bounds": { "width": width, "height": height, "windowState": "normal" } }),
)
.await?;
Ok(())
}
pub async fn viewport(&self, width: u32, height: u32) -> Result<()> {
self.core
.send(
"Emulation.setDeviceMetricsOverride",
json!({ "width": width, "height": height, "deviceScaleFactor": 0, "mobile": false }),
)
.await?;
Ok(())
}
pub async fn max(&self) -> Result<()> {
let id = self.window_id().await?;
self.core
.send(
"Browser.setWindowBounds",
json!({ "windowId": id, "bounds": { "windowState": "maximized" } }),
)
.await?;
Ok(())
}
}
pub struct ChromiumWait {
core: Arc<CdpCore>,
}
impl ChromiumWait {
pub(crate) fn new(core: Arc<CdpCore>) -> Self {
Self { core }
}
fn deadline(&self, timeout: Option<Duration>) -> Instant {
Instant::now() + timeout.unwrap_or_else(|| self.core.timeout())
}
pub async fn doc_loaded(&self, timeout: Option<Duration>) -> Result<bool> {
let deadline = self.deadline(timeout);
loop {
let rs = self
.core
.eval_value("document.readyState")
.await
.ok()
.and_then(|v| v.as_str().map(str::to_string))
.unwrap_or_default();
if rs == "complete" {
return Ok(true);
}
if Instant::now() >= deadline {
return Ok(false);
}
sleep(Duration::from_millis(80)).await;
}
}
pub async fn ele_displayed(&self, selector: &str, timeout: Option<Duration>) -> Result<bool> {
let deadline = self.deadline(timeout);
loop {
if let Some(oid) = self
.core
.eval_handle(&doc_query_expr(selector, true))
.await?
{
let el = ChromiumElement::new(self.core.clone(), oid);
if el.is_displayed().await.unwrap_or(false) {
return Ok(true);
}
}
if Instant::now() >= deadline {
return Ok(false);
}
sleep(Duration::from_millis(80)).await;
}
}
pub async fn ele_deleted(&self, selector: &str, timeout: Option<Duration>) -> Result<bool> {
let deadline = self.deadline(timeout);
loop {
let gone = self
.core
.eval_handle(&doc_query_expr(selector, true))
.await?
.is_none();
if gone {
return Ok(true);
}
if Instant::now() >= deadline {
return Ok(false);
}
sleep(Duration::from_millis(80)).await;
}
}
pub async fn title_contains(&self, sub: &str, timeout: Option<Duration>) -> Result<bool> {
let deadline = self.deadline(timeout);
loop {
let t = self
.core
.eval_value("document.title")
.await
.ok()
.and_then(|v| v.as_str().map(str::to_string))
.unwrap_or_default();
if t.contains(sub) {
return Ok(true);
}
if Instant::now() >= deadline {
return Ok(false);
}
sleep(Duration::from_millis(80)).await;
}
}
pub async fn url_contains(&self, sub: &str, timeout: Option<Duration>) -> Result<bool> {
let deadline = self.deadline(timeout);
loop {
let u = self
.core
.eval_value("location.href")
.await
.ok()
.and_then(|v| v.as_str().map(str::to_string))
.unwrap_or_default();
if u.contains(sub) {
return Ok(true);
}
if Instant::now() >= deadline {
return Ok(false);
}
sleep(Duration::from_millis(80)).await;
}
}
}
pub(crate) fn cookie_param_json(c: &CookieParam) -> Value {
let mut o = json!({ "name": c.name, "value": c.value });
if let Some(u) = &c.url {
o["url"] = json!(u);
}
if let Some(d) = &c.domain {
o["domain"] = json!(d);
}
if let Some(p) = &c.path {
o["path"] = json!(p);
}
if let Some(v) = c.secure {
o["secure"] = json!(v);
}
if let Some(v) = c.http_only {
o["httpOnly"] = json!(v);
}
if let Some(v) = c.expires {
o["expires"] = json!(v);
}
o
}