#![allow(clippy::missing_errors_doc)]
pub(crate) mod async_tempdir;
pub mod cdp;
pub(crate) mod json_scan;
pub(crate) mod process;
pub mod webkit;
pub mod bidi;
#[inline]
pub(crate) fn empty_params() -> serde_json::Value {
serde_json::Value::Object(serde_json::Map::new())
}
use crate::console_message::ConsoleMessage;
use crate::error::Result;
use crate::events::EventEmitter;
use crate::network::Request as NetworkRequest;
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Clone, Default)]
pub struct PageBackref {
inner: Arc<std::sync::Mutex<std::sync::Weak<crate::page::Page>>>,
}
impl PageBackref {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn set(&self, weak: std::sync::Weak<crate::page::Page>) {
if let Ok(mut guard) = self.inner.lock() {
*guard = weak;
}
}
#[must_use]
pub fn upgrade(&self) -> Option<Arc<crate::page::Page>> {
self.inner.lock().ok()?.upgrade()
}
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct FrameInfo {
pub frame_id: String,
pub parent_frame_id: Option<String>,
pub name: String,
pub url: String,
}
#[derive(Debug, Clone)]
pub struct AxNodeData {
pub node_id: String,
pub parent_id: Option<String>,
pub backend_dom_node_id: Option<i64>,
pub ignored: bool,
pub role: Option<String>,
pub name: Option<String>,
pub description: Option<String>,
pub properties: Vec<AxProperty>,
}
#[derive(Debug, Clone)]
pub struct AxProperty {
pub name: String,
pub value: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum SameSite {
Strict,
Lax,
None,
}
impl SameSite {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Strict => "Strict",
Self::Lax => "Lax",
Self::None => "None",
}
}
}
impl std::str::FromStr for SameSite {
type Err = ();
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
"Strict" => Ok(Self::Strict),
"Lax" => Ok(Self::Lax),
"None" => Ok(Self::None),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CookieData {
pub name: String,
pub value: String,
pub domain: String,
pub path: String,
pub secure: bool,
pub http_only: bool,
pub expires: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub same_site: Option<SameSite>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SetCookieParams {
pub name: String,
pub value: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
#[serde(default)]
pub domain: String,
#[serde(default)]
pub path: String,
#[serde(default)]
pub secure: bool,
#[serde(default)]
pub http_only: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub same_site: Option<SameSite>,
}
impl From<SetCookieParams> for CookieData {
fn from(p: SetCookieParams) -> Self {
Self {
name: p.name,
value: p.value,
domain: p.domain,
path: if p.path.is_empty() { "/".to_string() } else { p.path },
secure: p.secure,
http_only: p.http_only,
expires: p.expires,
same_site: p.same_site,
url: p.url,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ClearCookieOptions {
pub name: Option<String>,
pub domain: Option<String>,
pub path: Option<String>,
}
#[derive(Debug, Clone)]
pub struct ScreenshotOpts {
pub format: ImageFormat,
pub quality: Option<i64>,
pub full_page: bool,
pub clip: Option<crate::options::ClipRect>,
pub omit_background: bool,
pub scale: Option<ScreenshotScale>,
pub animations: Option<ScreenshotAnimations>,
pub caret: Option<ScreenshotCaret>,
pub mask: Vec<String>,
pub mask_color: Option<String>,
pub style: Option<String>,
}
impl Default for ScreenshotOpts {
fn default() -> Self {
Self {
format: ImageFormat::Png,
quality: None,
full_page: false,
clip: None,
omit_background: false,
scale: None,
animations: None,
caret: None,
mask: Vec::new(),
mask_color: None,
style: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScreenshotScale {
Css,
Device,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScreenshotAnimations {
Disabled,
Allow,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScreenshotCaret {
Hide,
Initial,
}
pub mod screenshot_js {
use super::{ScreenshotAnimations, ScreenshotCaret, ScreenshotOpts};
#[must_use]
pub fn build_css(opts: &ScreenshotOpts) -> String {
let mut css = String::new();
if !matches!(opts.caret, Some(ScreenshotCaret::Initial)) {
css.push_str("* { caret-color: transparent !important; }");
}
if matches!(opts.animations, Some(ScreenshotAnimations::Disabled)) {
css.push_str(" *, *::before, *::after { animation-play-state: paused !important; transition: none !important; }");
}
if let Some(ref user) = opts.style {
css.push_str(user);
}
css
}
#[must_use]
pub fn install_style_js(css: &str) -> String {
let esc = css.replace('\\', "\\\\").replace('`', "\\`");
format!(
r"(function(){{
const s = document.createElement('style');
s.id = '__fd_screenshot_style__';
s.textContent = `{esc}`;
(document.head || document.documentElement).appendChild(s);
}})()"
)
}
#[must_use]
pub fn uninstall_style_js() -> &'static str {
"document.getElementById('__fd_screenshot_style__')?.remove()"
}
#[must_use]
pub fn install_mask_js(opts: &ScreenshotOpts) -> Option<String> {
if opts.mask.is_empty() {
return None;
}
let selectors_json = serde_json::to_string(&opts.mask).unwrap_or_else(|_| "[]".into());
let color = opts.mask_color.clone().unwrap_or_else(|| "#FF00FF".into());
let color_json = serde_json::to_string(&color).unwrap_or_else(|_| "\"#FF00FF\"".into());
Some(format!(
r"(function(){{
const selectors = {selectors_json};
const color = {color_json};
const tag = '__fd_mask_' + Math.random().toString(36).slice(2);
window.__fd_mask_tag = tag;
for (const sel of selectors) {{
try {{
const els = document.querySelectorAll(sel);
for (const el of els) {{
const r = el.getBoundingClientRect();
const o = document.createElement('div');
o.className = tag;
o.style.cssText = `all: initial; position: fixed; left: ${{r.left}}px; top: ${{r.top}}px; width: ${{r.width}}px; height: ${{r.height}}px; background: ${{color}}; z-index: 2147483647; pointer-events: none;`;
document.body.appendChild(o);
}}
}} catch (e) {{}}
}}
}})()"
))
}
#[must_use]
pub fn uninstall_mask_js() -> &'static str {
"(function(){const t=window.__fd_mask_tag;if(!t)return;document.querySelectorAll('.'+t).forEach(n=>n.remove());delete window.__fd_mask_tag;})()"
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ImageFormat {
Png,
Jpeg,
Webp,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MetricData {
pub name: String,
pub value: f64,
}
#[derive(Debug, Clone)]
pub struct BackendClickArgs {
pub button: crate::options::MouseButton,
pub click_count: u32,
pub delay_ms: u64,
pub modifiers_bitmask: u32,
pub modifiers: Vec<crate::options::Modifier>,
pub steps: u32,
}
impl BackendClickArgs {
#[must_use]
pub fn from_options(opts: &crate::options::ClickOptions) -> Self {
Self {
button: opts.resolved_button(),
click_count: opts.resolved_click_count(),
delay_ms: opts.resolved_delay_ms(),
modifiers_bitmask: crate::options::modifiers_bitmask(&opts.modifiers),
modifiers: opts.modifiers.clone(),
steps: opts.resolved_steps(),
}
}
#[must_use]
pub fn default_left() -> Self {
Self {
button: crate::options::MouseButton::Left,
click_count: 1,
delay_ms: 0,
modifiers_bitmask: 0,
modifiers: Vec::new(),
steps: 1,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct BackendHoverArgs {
pub modifiers_bitmask: u32,
pub steps: u32,
}
#[derive(Debug, Clone, Copy)]
pub struct BackendTapArgs {
pub modifiers_bitmask: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NavLifecycle {
Commit,
DomContentLoaded,
Load,
}
impl NavLifecycle {
#[must_use]
pub fn parse_lifecycle(s: &str) -> Self {
match s {
"commit" => Self::Commit,
"domcontentloaded" => Self::DomContentLoaded,
_ => Self::Load,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BackendKind {
CdpPipe,
CdpRaw,
WebKit,
Bidi,
}
#[derive(Clone)]
pub enum AnyBrowser {
CdpPipe(cdp::CdpBrowser<cdp::pipe::PipeTransport>),
CdpRaw(cdp::CdpBrowser<cdp::ws::WsTransport>),
WebKit(webkit::WebKitBrowser),
Bidi(bidi::BidiBrowser),
}
impl AnyBrowser {
pub async fn pages(&self) -> Result<Vec<AnyPage>> {
match self {
Self::CdpPipe(b) => Box::pin(b.pages()).await,
Self::CdpRaw(b) => Box::pin(b.pages()).await,
Self::WebKit(b) => Box::pin(b.pages()).await,
Self::Bidi(b) => Box::pin(b.pages()).await,
}
}
pub async fn new_context(&self, options: Option<&crate::options::BrowserContextOptions>) -> Result<String> {
let proxy = options.and_then(|o| o.proxy.as_ref());
match self {
Self::CdpPipe(b) => b.new_context(proxy).await,
Self::CdpRaw(b) => b.new_context(proxy).await,
Self::WebKit(b) => b.new_context_with_options(options).await,
Self::Bidi(b) => b.new_context(proxy).await,
}
}
pub async fn dispose_context(&self, browser_context_id: &str) -> Result<()> {
match self {
Self::CdpPipe(b) => b.dispose_context(browser_context_id).await,
Self::CdpRaw(b) => b.dispose_context(browser_context_id).await,
Self::WebKit(b) => b.dispose_context(browser_context_id).await,
Self::Bidi(b) => b.dispose_context(browser_context_id).await,
}
}
pub async fn new_page(
&self,
url: &str,
browser_context_id: Option<&str>,
viewport: Option<&crate::options::ViewportConfig>,
) -> Result<AnyPage> {
match self {
Self::CdpPipe(b) => Box::pin(b.new_page(url, browser_context_id, viewport)).await,
Self::CdpRaw(b) => Box::pin(b.new_page(url, browser_context_id, viewport)).await,
Self::WebKit(b) => Box::pin(b.new_page(url, browser_context_id, viewport)).await,
Self::Bidi(b) => Box::pin(b.new_page(url, browser_context_id, viewport)).await,
}
}
pub async fn close(&mut self) -> Result<()> {
match self {
Self::CdpPipe(b) => b.close().await,
Self::CdpRaw(b) => b.close().await,
Self::WebKit(b) => b.close().await,
Self::Bidi(b) => b.close().await,
}
}
#[must_use]
pub fn version(&self) -> String {
match self {
Self::CdpPipe(b) => b.version().to_string(),
Self::CdpRaw(b) => b.version().to_string(),
Self::WebKit(b) => b.version(),
Self::Bidi(b) => b.version(),
}
}
}
#[derive(Clone)]
pub enum AnyPage {
CdpPipe(cdp::CdpPage<cdp::pipe::PipeTransport>),
CdpRaw(cdp::CdpPage<cdp::ws::WsTransport>),
WebKit(webkit::WebKitPage),
Bidi(bidi::BidiPage),
}
macro_rules! page_dispatch {
($self:expr, $method:ident ( $($arg:expr),* $(,)? )) => {
match $self {
AnyPage::CdpPipe(p) => p.$method($($arg),*).await,
AnyPage::CdpRaw(p) => p.$method($($arg),*).await,
AnyPage::WebKit(p) => p.$method($($arg),*).await,
AnyPage::Bidi(p) => p.$method($($arg),*).await,
}
};
}
impl AnyPage {
#[must_use]
pub fn events(&self) -> &EventEmitter {
match self {
AnyPage::CdpPipe(p) => &p.events,
AnyPage::CdpRaw(p) => &p.events,
AnyPage::WebKit(p) => &p.events,
AnyPage::Bidi(p) => &p.events,
}
}
#[must_use]
pub fn dialog_manager(&self) -> &crate::dialog::DialogManager {
match self {
AnyPage::CdpPipe(p) => &p.dialog_manager,
AnyPage::CdpRaw(p) => &p.dialog_manager,
AnyPage::WebKit(p) => &p.dialog_manager,
AnyPage::Bidi(p) => &p.dialog_manager,
}
}
#[must_use]
pub fn file_chooser_manager(&self) -> &crate::file_chooser::FileChooserManager {
match self {
AnyPage::CdpPipe(p) => &p.file_chooser_manager,
AnyPage::CdpRaw(p) => &p.file_chooser_manager,
AnyPage::WebKit(p) => &p.file_chooser_manager,
AnyPage::Bidi(p) => &p.file_chooser_manager,
}
}
#[must_use]
pub fn download_manager(&self) -> &crate::download::DownloadManager {
match self {
AnyPage::CdpPipe(p) => &p.download_manager,
AnyPage::CdpRaw(p) => &p.download_manager,
AnyPage::WebKit(p) => &p.download_manager,
AnyPage::Bidi(p) => &p.download_manager,
}
}
pub async fn enable_file_chooser_intercept(&self) -> Result<()> {
match self {
AnyPage::CdpPipe(p) => p.enable_file_chooser_intercept().await,
AnyPage::CdpRaw(p) => p.enable_file_chooser_intercept().await,
AnyPage::WebKit(p) => p.enable_file_chooser_intercept().await,
AnyPage::Bidi(_) => Ok(()),
}
}
pub async fn enable_download_behavior(&self) -> Result<()> {
match self {
AnyPage::CdpPipe(p) => p.enable_download_behavior().await,
AnyPage::CdpRaw(p) => p.enable_download_behavior().await,
AnyPage::WebKit(p) => p.enable_download_behavior().await,
AnyPage::Bidi(_) => Ok(()),
}
}
pub fn set_page_backref(&self, weak: std::sync::Weak<crate::page::Page>) {
let slot = match self {
AnyPage::CdpPipe(p) => &p.page_backref,
AnyPage::CdpRaw(p) => &p.page_backref,
AnyPage::WebKit(p) => &p.page_backref,
AnyPage::Bidi(p) => &p.page_backref,
};
slot.set(weak);
}
pub(crate) fn frame_cache(&self) -> &std::sync::Arc<std::sync::Mutex<crate::frame_cache::FrameCache>> {
match self {
AnyPage::CdpPipe(p) => &p.frame_cache,
AnyPage::CdpRaw(p) => &p.frame_cache,
AnyPage::WebKit(p) => &p.frame_cache,
AnyPage::Bidi(p) => &p.frame_cache,
}
}
pub(crate) fn frame_listener_started(&self) -> &std::sync::Arc<std::sync::atomic::AtomicBool> {
match self {
AnyPage::CdpPipe(p) => &p.frame_listener_started,
AnyPage::CdpRaw(p) => &p.frame_listener_started,
AnyPage::WebKit(p) => &p.frame_listener_started,
AnyPage::Bidi(p) => &p.frame_listener_started,
}
}
#[must_use]
pub fn kind(&self) -> BackendKind {
match self {
AnyPage::CdpPipe(_) => BackendKind::CdpPipe,
AnyPage::CdpRaw(_) => BackendKind::CdpRaw,
AnyPage::WebKit(_) => BackendKind::WebKit,
AnyPage::Bidi(_) => BackendKind::Bidi,
}
}
pub async fn get_frame_tree(&self) -> Result<Vec<FrameInfo>> {
page_dispatch!(self, get_frame_tree())
}
#[must_use]
pub fn peek_main_frame_id(&self) -> Option<String> {
match self {
Self::CdpPipe(p) => p.peek_main_frame_id(),
Self::CdpRaw(p) => p.peek_main_frame_id(),
Self::WebKit(p) => p.peek_main_frame_id(),
Self::Bidi(p) => Some(p.context_id.to_string()),
}
}
pub async fn evaluate_in_frame(&self, expression: &str, frame_id: &str) -> Result<Option<serde_json::Value>> {
page_dispatch!(self, evaluate_in_frame(expression, frame_id))
}
pub async fn content_frame_id(&self, object_id: &str) -> Result<Option<String>> {
match self {
AnyPage::CdpPipe(p) => p.content_frame_id(object_id).await,
AnyPage::CdpRaw(p) => p.content_frame_id(object_id).await,
AnyPage::WebKit(p) => p.content_frame_id(object_id).await,
AnyPage::Bidi(_) => Ok(None),
}
}
pub async fn goto(
&self,
url: &str,
lifecycle: NavLifecycle,
timeout_ms: u64,
referer: Option<&str>,
) -> Result<Option<crate::network::Response>> {
page_dispatch!(self, goto(url, lifecycle, timeout_ms, referer))
}
pub async fn wait_for_navigation(&self) -> Result<()> {
page_dispatch!(self, wait_for_navigation())
}
pub async fn reload(&self, lifecycle: NavLifecycle, timeout_ms: u64) -> Result<Option<crate::network::Response>> {
page_dispatch!(self, reload(lifecycle, timeout_ms))
}
pub async fn go_back(&self, lifecycle: NavLifecycle, timeout_ms: u64) -> Result<Option<crate::network::Response>> {
page_dispatch!(self, go_back(lifecycle, timeout_ms))
}
pub async fn go_forward(&self, lifecycle: NavLifecycle, timeout_ms: u64) -> Result<Option<crate::network::Response>> {
page_dispatch!(self, go_forward(lifecycle, timeout_ms))
}
pub async fn url(&self) -> Result<Option<String>> {
page_dispatch!(self, url())
}
pub async fn title(&self) -> Result<Option<String>> {
page_dispatch!(self, title())
}
pub async fn injected_script(&self) -> Result<String> {
page_dispatch!(self, injected_script())
}
pub async fn ensure_engine_injected(&self) -> Result<()> {
page_dispatch!(self, ensure_engine_injected())
}
pub async fn evaluate(&self, expression: &str) -> Result<Option<serde_json::Value>> {
page_dispatch!(self, evaluate(expression))
}
pub async fn find_element(&self, selector: &str) -> Result<AnyElement> {
page_dispatch!(self, find_element(selector))
}
pub async fn evaluate_to_element(&self, js: &str, frame_id: Option<&str>) -> Result<AnyElement> {
page_dispatch!(self, evaluate_to_element(js, frame_id))
}
pub async fn content(&self) -> Result<String> {
page_dispatch!(self, content())
}
pub async fn set_content(&self, html: &str) -> Result<()> {
page_dispatch!(self, set_content(html))
}
pub async fn screenshot(&self, opts: ScreenshotOpts) -> Result<Vec<u8>> {
page_dispatch!(self, screenshot(opts))
}
pub async fn accessibility_tree(&self) -> Result<Vec<AxNodeData>> {
page_dispatch!(self, accessibility_tree())
}
pub async fn accessibility_tree_with_depth(&self, depth: i32) -> Result<Vec<AxNodeData>> {
page_dispatch!(self, accessibility_tree_with_depth(depth))
}
pub async fn click_at(&self, x: f64, y: f64) -> Result<()> {
page_dispatch!(self, click_at(x, y))
}
pub async fn click_at_opts(&self, x: f64, y: f64, button: &str, click_count: u32) -> Result<()> {
page_dispatch!(self, click_at_opts(x, y, button, click_count))
}
pub async fn click_at_with(&self, x: f64, y: f64, args: &BackendClickArgs) -> Result<()> {
page_dispatch!(self, click_at_with(x, y, args))
}
pub async fn hover_at_with(&self, x: f64, y: f64, args: &BackendHoverArgs) -> Result<()> {
page_dispatch!(self, hover_at_with(x, y, args))
}
pub async fn tap_at_with(&self, x: f64, y: f64, args: &BackendTapArgs) -> Result<()> {
page_dispatch!(self, tap_at_with(x, y, args))
}
pub async fn press_modifiers(&self, mods: &[crate::options::Modifier]) -> Result<()> {
page_dispatch!(self, press_modifiers(mods))
}
pub async fn release_modifiers(&self, mods: &[crate::options::Modifier]) -> Result<()> {
page_dispatch!(self, release_modifiers(mods))
}
pub async fn move_mouse(&self, x: f64, y: f64) -> Result<()> {
page_dispatch!(self, move_mouse(x, y))
}
pub async fn move_mouse_smooth(&self, from_x: f64, from_y: f64, to_x: f64, to_y: f64, steps: u32) -> Result<()> {
page_dispatch!(self, move_mouse_smooth(from_x, from_y, to_x, to_y, steps))
}
pub async fn mouse_wheel(&self, delta_x: f64, delta_y: f64) -> Result<()> {
page_dispatch!(self, mouse_wheel(delta_x, delta_y))
}
pub async fn mouse_down(&self, x: f64, y: f64, button: &str) -> Result<()> {
page_dispatch!(self, mouse_down(x, y, button))
}
pub async fn mouse_up(&self, x: f64, y: f64, button: &str) -> Result<()> {
page_dispatch!(self, mouse_up(x, y, button))
}
pub async fn click_and_drag(&self, from: (f64, f64), to: (f64, f64), steps: u32) -> Result<()> {
page_dispatch!(self, click_and_drag(from, to, steps))
}
pub async fn type_str(&self, text: &str) -> Result<()> {
page_dispatch!(self, type_str(text))
}
pub async fn insert_text(&self, text: &str) -> Result<()> {
self.type_str(text).await
}
pub async fn key_down(&self, key: &str) -> Result<()> {
page_dispatch!(self, key_down(key))
}
pub async fn key_up(&self, key: &str) -> Result<()> {
page_dispatch!(self, key_up(key))
}
pub async fn press_key(&self, key: &str) -> Result<()> {
page_dispatch!(self, press_key(key))
}
pub async fn get_cookies(&self) -> Result<Vec<CookieData>> {
page_dispatch!(self, get_cookies())
}
pub async fn set_cookie(&self, cookie: CookieData) -> Result<()> {
page_dispatch!(self, set_cookie(cookie))
}
pub async fn delete_cookie(&self, name: &str, domain: Option<&str>) -> Result<()> {
page_dispatch!(self, delete_cookie(name, domain))
}
pub async fn clear_cookies(&self) -> Result<()> {
page_dispatch!(self, clear_cookies())
}
pub async fn clear_cookies_filtered(&self, options: &ClearCookieOptions) -> Result<()> {
if options.name.is_none() && options.domain.is_none() && options.path.is_none() {
return self.clear_cookies().await;
}
let cookies = self.get_cookies().await?;
for c in &cookies {
let name_match = options.name.as_ref().is_none_or(|n| &c.name == n);
let domain_match = options.domain.as_ref().is_none_or(|d| &c.domain == d);
let path_match = options.path.as_ref().is_none_or(|p| &c.path == p);
if name_match && domain_match && path_match {
self.delete_cookie(&c.name, Some(&c.domain)).await?;
}
}
Ok(())
}
pub async fn apply_context_options(&self, opts: &crate::options::BrowserContextOptions) -> Result<()> {
page_dispatch!(self, apply_context_options(opts))
}
pub async fn emulate_viewport(&self, config: &crate::options::ViewportConfig) -> Result<()> {
page_dispatch!(self, emulate_viewport(config))
}
pub async fn emulate_media(&self, opts: &crate::options::EmulateMediaOptions) -> Result<()> {
page_dispatch!(self, emulate_media(opts))
}
pub async fn set_extra_http_headers(&self, headers: &rustc_hash::FxHashMap<String, String>) -> Result<()> {
page_dispatch!(self, set_extra_http_headers(headers))
}
pub async fn reset_permissions(&self) -> Result<()> {
page_dispatch!(self, reset_permissions())
}
pub async fn start_tracing(&self) -> Result<()> {
page_dispatch!(self, start_tracing())
}
pub async fn stop_tracing(&self) -> Result<()> {
page_dispatch!(self, stop_tracing())
}
pub async fn metrics(&self) -> Result<Vec<MetricData>> {
page_dispatch!(self, metrics())
}
pub async fn resolve_backend_node(&self, backend_node_id: i64, ref_id: &str) -> Result<AnyElement> {
page_dispatch!(self, resolve_backend_node(backend_node_id, ref_id))
}
pub fn attach_listeners(
&self,
console_log: Arc<RwLock<Vec<ConsoleMessage>>>,
network_log: Arc<RwLock<Vec<NetworkRequest>>>,
dialog_log: Arc<RwLock<Vec<crate::state::DialogEvent>>>,
) {
match self {
Self::CdpPipe(p) => p.attach_listeners(console_log, network_log, dialog_log),
Self::CdpRaw(p) => p.attach_listeners(console_log, network_log, dialog_log),
Self::WebKit(p) => p.attach_listeners(console_log, network_log, dialog_log),
Self::Bidi(p) => p.attach_listeners(console_log, network_log, dialog_log),
}
}
pub async fn screenshot_element(&self, selector: &str, format: ImageFormat) -> Result<Vec<u8>> {
page_dispatch!(self, screenshot_element(selector, format))
}
pub async fn pdf(&self, opts: crate::options::PdfOptions) -> Result<Vec<u8>> {
page_dispatch!(self, pdf(opts))
}
pub async fn start_screencast(
&self,
quality: u8,
max_width: u32,
max_height: u32,
) -> Result<(
tokio::sync::mpsc::UnboundedReceiver<(Vec<u8>, f64)>,
tokio::sync::oneshot::Sender<()>,
)> {
match self {
AnyPage::CdpPipe(p) => p.start_screencast(quality, max_width, max_height).await,
AnyPage::CdpRaw(p) => p.start_screencast(quality, max_width, max_height).await,
AnyPage::WebKit(p) => {
let rx = p.start_screencast(quality, max_width, max_height).await?;
let (tx, _rx_shutdown) = tokio::sync::oneshot::channel();
Ok((rx, tx))
},
AnyPage::Bidi(p) => {
let rx = p.start_screencast(quality, max_width, max_height).await?;
let (tx, _rx_shutdown) = tokio::sync::oneshot::channel();
Ok((rx, tx))
},
}
}
pub async fn stop_screencast(&self) -> Result<()> {
match self {
AnyPage::CdpPipe(p) => p.stop_screencast().await,
AnyPage::CdpRaw(p) => p.stop_screencast().await,
AnyPage::WebKit(p) => p.stop_screencast().await,
AnyPage::Bidi(p) => p.stop_screencast().await,
}
}
pub async fn set_file_input(&self, selector: &str, paths: &[String]) -> Result<()> {
page_dispatch!(self, set_file_input(selector, paths))
}
pub async fn route(
&self,
matcher: crate::url_matcher::UrlMatcher,
handler: crate::route::RouteHandler,
) -> Result<()> {
page_dispatch!(self, route(matcher, handler))
}
pub async fn unroute(&self, matcher: &crate::url_matcher::UrlMatcher) -> Result<()> {
page_dispatch!(self, unroute(matcher))
}
pub async fn close_page(&self, opts: crate::options::PageCloseOptions) -> Result<()> {
page_dispatch!(self, close_page(opts))
}
#[must_use]
pub fn is_closed(&self) -> bool {
match self {
Self::CdpPipe(p) => p.is_closed(),
Self::CdpRaw(p) => p.is_closed(),
Self::WebKit(p) => p.is_closed(),
Self::Bidi(p) => p.is_closed(),
}
}
pub async fn expose_function(&self, name: &str, func: crate::events::ExposedFn) -> Result<()> {
page_dispatch!(self, expose_function(name, func))
}
pub async fn remove_exposed_function(&self, name: &str) -> Result<()> {
page_dispatch!(self, remove_exposed_function(name))
}
pub async fn add_init_script(&self, source: &str) -> Result<String> {
page_dispatch!(self, add_init_script(source))
}
pub async fn remove_init_script(&self, identifier: &str) -> Result<()> {
page_dispatch!(self, remove_init_script(identifier))
}
#[allow(clippy::too_many_arguments)]
pub async fn call_utility_evaluate(
&self,
fn_source: &str,
args: &[crate::protocol::SerializedValue],
handles: &[crate::protocol::HandleId],
frame_id: Option<&str>,
is_function: Option<bool>,
return_by_value: bool,
) -> crate::error::Result<crate::js_handle::EvaluateResult> {
match self {
Self::CdpPipe(p) => {
p.call_utility_evaluate(fn_source, args, handles, frame_id, is_function, return_by_value)
.await
},
Self::CdpRaw(p) => {
p.call_utility_evaluate(fn_source, args, handles, frame_id, is_function, return_by_value)
.await
},
Self::WebKit(p) => {
p.call_utility_evaluate(fn_source, args, handles, frame_id, is_function, return_by_value)
.await
},
Self::Bidi(p) => {
p.call_utility_evaluate(fn_source, args, handles, frame_id, is_function, return_by_value)
.await
},
}
}
pub async fn release_handle(&self, remote: &crate::js_handle::HandleRemote) -> crate::error::Result<()> {
use crate::js_handle::HandleRemote;
match (self, remote) {
(Self::CdpPipe(p), HandleRemote::Cdp(obj)) => p.release_object(obj).await,
(Self::CdpRaw(p), HandleRemote::Cdp(obj)) => p.release_object(obj).await,
(Self::WebKit(p), HandleRemote::WebKit(obj)) => p.release_object(obj).await,
(Self::Bidi(p), HandleRemote::Bidi { shared_id, handle }) => p.release_handle(shared_id, handle.as_deref()).await,
(page, remote) => Err(crate::error::FerriError::Backend(format!(
"handle/backend mismatch: {:?} handle on {:?} backend",
remote,
page.kind()
))),
}
}
}
pub fn element_from_remote(
page: &AnyPage,
remote: &crate::js_handle::HandleRemote,
) -> crate::error::Result<AnyElement> {
use crate::js_handle::HandleRemote;
match (page, remote) {
(AnyPage::CdpPipe(p), HandleRemote::Cdp(obj)) => Ok(AnyElement::CdpPipe(p.element_from_object_id(obj.clone()))),
(AnyPage::CdpRaw(p), HandleRemote::Cdp(obj)) => Ok(AnyElement::CdpRaw(p.element_from_object_id(obj.clone()))),
(AnyPage::WebKit(p), HandleRemote::WebKit(obj)) => {
Ok(AnyElement::WebKit(p.element_from_object_id(obj.to_string())))
},
(AnyPage::Bidi(p), HandleRemote::Bidi { shared_id, .. }) => {
Ok(AnyElement::Bidi(p.element_from_shared_id(shared_id.clone())))
},
(page, remote) => Err(crate::error::FerriError::Backend(format!(
"element_from_remote: backend mismatch — {:?} remote on {:?} backend",
remote,
page.kind()
))),
}
}
pub async fn element_handle_remote(element: &AnyElement) -> crate::error::Result<crate::js_handle::HandleRemote> {
use crate::js_handle::HandleRemote;
match element {
AnyElement::CdpPipe(e) => {
let obj = e.ensure_object_id().await?;
Ok(HandleRemote::Cdp(obj))
},
AnyElement::CdpRaw(e) => {
let obj = e.ensure_object_id().await?;
Ok(HandleRemote::Cdp(obj))
},
AnyElement::WebKit(e) => Ok(HandleRemote::WebKit(std::sync::Arc::from(e.object_id()))),
AnyElement::Bidi(e) => Ok(HandleRemote::Bidi {
shared_id: e.shared_id.clone(),
handle: None,
}),
}
}
pub enum AnyElement {
CdpPipe(cdp::CdpElement<cdp::pipe::PipeTransport>),
CdpRaw(cdp::CdpElement<cdp::ws::WsTransport>),
WebKit(webkit::WebKitElement),
Bidi(bidi::BidiElement),
}
macro_rules! element_dispatch {
($self:expr, $method:ident ( $($arg:expr),* $(,)? )) => {
match $self {
AnyElement::CdpPipe(e) => e.$method($($arg),*).await,
AnyElement::CdpRaw(e) => e.$method($($arg),*).await,
AnyElement::WebKit(e) => e.$method($($arg),*).await,
AnyElement::Bidi(e) => e.$method($($arg),*).await,
}
};
}
impl AnyElement {
pub async fn click(&self) -> Result<()> {
element_dispatch!(self, click())
}
pub async fn dblclick(&self) -> Result<()> {
element_dispatch!(self, dblclick())
}
pub async fn hover(&self) -> Result<()> {
element_dispatch!(self, hover())
}
pub async fn type_str(&self, text: &str) -> Result<()> {
element_dispatch!(self, type_str(text))
}
pub async fn call_js_fn(&self, function: &str) -> Result<()> {
element_dispatch!(self, call_js_fn(function))
}
pub async fn call_js_fn_value(&self, function: &str) -> Result<Option<serde_json::Value>> {
element_dispatch!(self, call_js_fn_value(function))
}
pub async fn scroll_into_view(&self) -> Result<()> {
element_dispatch!(self, scroll_into_view())
}
pub async fn screenshot(&self, format: ImageFormat) -> Result<Vec<u8>> {
element_dispatch!(self, screenshot(format))
}
}