mod chromium;
use anyhow::Result;
use serde::de::DeserializeOwned;
use serde_json::Value;
pub struct LaunchOptions {
pub width: u32,
pub height: u32,
pub extra_args: Vec<String>,
}
impl LaunchOptions {
pub fn new(width: u32, height: u32) -> Self {
Self {
width,
height,
extra_args: Vec::new(),
}
}
pub fn arg(mut self, arg: impl Into<String>) -> Self {
self.extra_args.push(arg.into());
self
}
}
#[async_trait::async_trait]
pub trait BrowserEngine: Send {
async fn new_page(&self, url: &str) -> Result<Box<dyn BrowserPage>>;
async fn close(&mut self);
}
#[async_trait::async_trait]
pub trait BrowserPage: Send + Sync {
async fn goto(&self, url: &str) -> Result<()>;
async fn evaluate_value(&self, js: &str) -> Result<Value>;
async fn try_evaluate_value(&self, js: &str) -> Option<Value>;
async fn execute(&self, js: &str);
async fn screenshot(&self, full_page: bool) -> Result<Vec<u8>>;
async fn find_and_click(&self, selector: &str) -> Result<()>;
async fn mouse_move(&self, x: f64, y: f64) -> Result<()>;
}
pub async fn evaluate<T: DeserializeOwned>(page: &dyn BrowserPage, js: &str) -> Result<T> {
let val = page.evaluate_value(js).await?;
serde_json::from_value(val).map_err(|e| anyhow::anyhow!("deserialize failed: {e}"))
}
pub async fn try_evaluate<T: DeserializeOwned>(page: &dyn BrowserPage, js: &str) -> Option<T> {
page.try_evaluate_value(js)
.await
.and_then(|v| serde_json::from_value(v).ok())
}
pub async fn launch(opts: LaunchOptions) -> Result<Box<dyn BrowserEngine>> {
Ok(Box::new(chromium::ChromiumEngine::launch(opts).await?))
}