use std::collections::HashMap;
pub mod error;
pub use error::{Error, Result};
#[cfg(feature = "cdp")]
pub mod cdp;
#[cfg(feature = "simple")]
pub mod simple;
#[cfg(feature = "rfengine")]
pub mod rfengine;
#[cfg(feature = "rfengine")]
pub mod rendering;
pub mod platform;
#[cfg(feature = "cdp")]
pub mod async_api;
#[cfg(feature = "cdp")]
pub use async_api::Browser;
#[derive(Debug, Clone)]
pub struct EngineConfig {
pub user_agent: String,
pub viewport: Viewport,
pub timeout_ms: u64,
pub headers: HashMap<String, String>,
pub enable_javascript: bool,
pub enable_js_isolation: bool,
pub enable_images: bool,
pub use_process_worker: bool,
pub script_timeout_ms: u64,
pub script_loop_iteration_limit: u64,
pub script_recursion_limit: usize,
pub cdp_chrome_executable: Option<String>,
pub cdp_ws_url: Option<String>,
pub enable_persistent_runtime: bool,
pub stylesheet_fetch_concurrency: usize,
pub enable_preconnect: bool,
pub wait_for_stylesheets_on_load: bool,
}
impl Default for EngineConfig {
fn default() -> Self {
Self {
user_agent: "Mozilla/5.0 (X11; Linux x86_64) Gecko/20100101 Firefox/115.0 RFOX/0.3"
.to_string(),
viewport: Viewport::default(),
timeout_ms: 30000,
headers: HashMap::new(),
enable_javascript: true,
enable_js_isolation: true,
enable_images: true,
use_process_worker: false,
script_timeout_ms: 5000,
script_loop_iteration_limit: 1000000,
script_recursion_limit: 1024,
cdp_chrome_executable: None,
cdp_ws_url: None,
enable_persistent_runtime: true,
stylesheet_fetch_concurrency: std::cmp::min(32, num_cpus::get().saturating_mul(4)),
enable_preconnect: true,
wait_for_stylesheets_on_load: true,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Viewport {
pub width: u32,
pub height: u32,
}
impl Default for Viewport {
fn default() -> Self {
Self {
width: 1280,
height: 720,
}
}
}
#[derive(Debug, Clone)]
pub struct TextSnapshot {
pub title: String,
pub text: String,
pub url: String,
}
#[derive(Debug, Clone)]
pub struct ScriptResult {
pub value: String,
pub is_error: bool,
}
#[derive(Debug, Clone)]
pub struct ConsoleMessage {
pub level: String,
pub text: String,
pub source: Option<String>,
pub line: Option<u32>,
pub column: Option<u32>,
pub stack: Option<String>,
}
#[derive(Debug, Clone)]
pub struct RequestInfo {
pub request_id: String,
pub url: String,
pub method: String,
pub resource_type: Option<String>,
pub headers: std::collections::HashMap<String, String>,
}
#[derive(Debug, Clone)]
pub struct Cookie {
pub name: String,
pub value: String,
pub domain: Option<String>,
pub path: Option<String>,
pub expires: Option<u64>,
pub size: Option<u32>,
pub http_only: Option<bool>,
pub secure: Option<bool>,
pub same_site: Option<String>,
}
#[derive(Debug, Clone)]
pub struct CookieParam {
pub name: String,
pub value: String,
pub url: Option<String>,
pub domain: Option<String>,
pub path: Option<String>,
pub secure: Option<bool>,
pub http_only: Option<bool>,
pub same_site: Option<String>,
pub expires: Option<u64>,
}
#[derive(Debug, Clone)]
pub enum RequestAction {
Continue,
Fail { error_reason: String },
Fulfill {
status: u16,
headers: std::collections::HashMap<String, String>,
body: Vec<u8>,
},
}
pub trait Engine {
fn new(config: EngineConfig) -> Result<Self>
where
Self: Sized;
fn load_url(&mut self, url: &str) -> Result<()>;
fn render_text_snapshot(&self) -> Result<TextSnapshot>;
fn render_png(&self) -> Result<Vec<u8>>;
fn evaluate_script(&mut self, script: &str) -> Result<ScriptResult>;
fn evaluate_script_in_page(&mut self, script: &str) -> Result<ScriptResult> {
self.evaluate_script(script)
}
fn on_load<F>(&mut self, cb: F)
where
F: Fn(&TextSnapshot) + Send + Sync + 'static;
fn clear_on_load(&mut self);
fn on_console<F>(&mut self, cb: F)
where
F: Fn(&ConsoleMessage) + Send + Sync + 'static;
fn clear_on_console(&mut self);
fn on_request<F>(&mut self, cb: F)
where
F: Fn(&RequestInfo) -> RequestAction + Send + Sync + 'static;
fn clear_on_request(&mut self);
fn get_cookies(&self) -> Result<Vec<Cookie>>;
fn set_cookies(&mut self, cookies: Vec<CookieParam>) -> Result<()>;
fn delete_cookie(
&mut self,
name: &str,
url: Option<&str>,
domain: Option<&str>,
path: Option<&str>,
) -> Result<()>;
fn clear_cookies(&mut self) -> Result<()>;
fn set_cookie_simple(
&mut self,
name: &str,
value: &str,
url: Option<&str>,
domain: Option<&str>,
path: Option<&str>,
expires: Option<u64>,
) -> Result<()> {
let param = CookieParam {
name: name.to_string(),
value: value.to_string(),
url: url.map(|s| s.to_string()),
domain: domain.map(|s| s.to_string()),
path: path.map(|s| s.to_string()),
secure: None,
http_only: None,
same_site: None,
expires,
};
self.set_cookies(vec![param])
}
fn get_cookie_simple(&self, name: &str) -> Result<Option<Cookie>> {
let cookies = self.get_cookies()?;
Ok(cookies.into_iter().find(|c| c.name == name))
}
fn clear_cookies_for_domain(&mut self, domain: &str) -> Result<()> {
let cookies = self.get_cookies()?;
for c in cookies
.into_iter()
.filter(|c| c.domain.as_deref() == Some(domain))
{
let _ = self.delete_cookie(&c.name, None, Some(domain), c.path.as_deref());
}
Ok(())
}
fn close(self) -> Result<()>;
}
#[cfg(feature = "rfengine")]
pub fn new_engine(config: EngineConfig) -> Result<impl Engine> {
rfengine::RFEngine::new(config)
}
#[cfg(all(not(feature = "rfengine"), feature = "cdp"))]
pub fn new_engine(config: EngineConfig) -> Result<impl Engine> {
cdp::CdpEngine::new(config)
}
#[cfg(all(not(feature = "rfengine"), not(feature = "cdp"), feature = "simple"))]
pub fn new_engine(config: EngineConfig) -> Result<impl Engine> {
simple::SimpleEngine::new(config)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = EngineConfig::default();
assert_eq!(config.viewport.width, 1280);
assert_eq!(config.viewport.height, 720);
assert!(config.enable_javascript);
}
#[test]
fn test_viewport() {
let viewport = Viewport {
width: 1920,
height: 1080,
};
assert_eq!(viewport.width, 1920);
assert_eq!(viewport.height, 1080);
}
}