use std::path::Path;
use crate::backend::BackendKind;
use crate::browser::Browser;
use crate::context::ContextRef;
use crate::error::Result;
use crate::options::{
BrowserKind, BrowserTypeOptions, ChromiumTransport, ConnectOptions, ConnectOverCdpOptions, LaunchOptions,
LaunchPersistentContextOptions, LaunchPlan,
};
use crate::state::{BrowserState, ConnectMode};
#[derive(Debug, Clone, Copy)]
pub struct BrowserType {
kind: BrowserKind,
transport: Option<ChromiumTransport>,
}
impl BrowserType {
#[must_use]
pub fn chromium() -> Self {
Self {
kind: BrowserKind::Chromium,
transport: None,
}
}
#[must_use]
pub fn chromium_with(opts: &BrowserTypeOptions) -> Self {
Self {
kind: BrowserKind::Chromium,
transport: opts.transport,
}
}
#[must_use]
pub fn firefox() -> Self {
Self {
kind: BrowserKind::Firefox,
transport: None,
}
}
#[must_use]
pub fn webkit() -> Self {
Self {
kind: BrowserKind::WebKit,
transport: None,
}
}
#[must_use]
pub fn with_backend(kind: BrowserKind, backend: BackendKind) -> Self {
let transport = match (kind, backend) {
(BrowserKind::Chromium, BackendKind::CdpRaw) => Some(ChromiumTransport::Ws),
(BrowserKind::Chromium, BackendKind::CdpPipe) => Some(ChromiumTransport::Pipe),
_ => None,
};
Self { kind, transport }
}
#[must_use]
pub fn name(self) -> &'static str {
self.kind.name()
}
#[must_use]
pub fn kind(self) -> BrowserKind {
self.kind
}
#[must_use]
pub fn executable_path(self) -> Option<std::path::PathBuf> {
match self.kind {
BrowserKind::Firefox => std::env::var("FIREFOX_PATH")
.ok()
.map(std::path::PathBuf::from)
.or_else(|| crate::state::detect_firefox().ok().map(std::path::PathBuf::from)),
BrowserKind::Chromium => Some(std::path::PathBuf::from(crate::state::resolve_chromium(true))),
BrowserKind::WebKit => None,
}
}
pub async fn launch(self, options: LaunchOptions) -> Result<Browser> {
let plan = LaunchPlan::from_public(self.kind, self.transport, options);
let mut state = BrowserState::with_plan(ConnectMode::Launch, plan);
Box::pin(state.ensure_browser()).await?;
Ok(Browser::from_state(state))
}
pub async fn connect(self, ws_endpoint: &str, options: ConnectOptions) -> Result<Browser> {
let cdp_opts = ConnectOverCdpOptions {
headers: options.headers,
slow_mo: options.slow_mo,
timeout: options.timeout,
};
self.connect_over_cdp(ws_endpoint, cdp_opts).await
}
pub async fn connect_over_cdp(self, endpoint_url: &str, _options: ConnectOverCdpOptions) -> Result<Browser> {
if self.kind != BrowserKind::Chromium {
return Err(crate::error::FerriError::Unsupported(format!(
"connectOverCDP is only supported for Chromium ({} cannot use the Chrome DevTools Protocol)",
self.kind.name()
)));
}
let plan = LaunchPlan {
backend: BackendKind::CdpRaw,
kind: BrowserKind::Chromium,
ws_endpoint: Some(endpoint_url.to_string()),
..LaunchPlan::default()
};
let mut state = BrowserState::with_plan(ConnectMode::ConnectUrl(endpoint_url.to_string()), plan);
Box::pin(state.ensure_browser()).await?;
Ok(Browser::from_state(state))
}
pub async fn launch_persistent_context(
&self,
user_data_dir: &Path,
options: LaunchPersistentContextOptions,
) -> Result<ContextRef> {
let LaunchPersistentContextOptions { launch, context } = options;
let mut plan = LaunchPlan::from_public(self.kind, self.transport, launch);
plan.user_data_dir = Some(user_data_dir.to_string_lossy().into_owned());
let mut state = BrowserState::with_plan(ConnectMode::Launch, plan);
state.persistent_context = true;
Box::pin(state.ensure_browser()).await?;
let browser = Browser::from_state(state);
let default_ctx = browser.default_context();
let composite = default_ctx.key.to_composite();
if let Some(rv) = context.record_video.clone() {
browser.state().read().await.set_record_video(&composite, rv);
}
browser.state().read().await.set_context_options(&composite, context);
Ok(default_ctx)
}
}
#[must_use]
pub fn chromium() -> BrowserType {
BrowserType::chromium()
}
#[must_use]
pub fn firefox() -> BrowserType {
BrowserType::firefox()
}
#[must_use]
pub fn webkit() -> BrowserType {
BrowserType::webkit()
}