use std::sync::{Arc, Mutex, RwLock, Weak};
use std::thread;
use std::time::Duration;
use std::{
collections::HashMap,
sync::atomic::{AtomicBool, Ordering},
};
use anyhow::{Error, Result};
use thiserror::Error;
use log::{debug, error, info, trace, warn};
use serde::de::DeserializeOwned;
use serde::Serialize;
use serde_json::{json, Value as Json};
use element::Element;
use point::Point;
use crate::protocol::cdp::{
types::{Event, Method},
Browser, Debugger, Emulation, Fetch, Input, Log, Network, Page, Profiler, Runtime, Target, DOM,
};
use Runtime::AddBinding;
use Input::DispatchKeyEvent;
use Page::{AddScriptToEvaluateOnNewDocument, Navigate, SetInterceptFileChooserDialog};
use Target::AttachToTarget;
use DOM::{Node, NodeId};
use Target::{TargetID, TargetInfo};
use Log::ViolationSetting;
use Fetch::{
events::RequestPausedEvent, AuthChallengeResponse, ContinueRequest, ContinueWithAuth,
FailRequest, FulfillRequest,
};
use Network::{
events::ResponseReceivedEventParams, Cookie, GetResponseBody, GetResponseBodyReturnObject,
SetExtraHTTPHeaders, SetUserAgentOverride,
};
use crate::util;
use crate::types::{Bounds, CurrentBounds, PrintToPdfOptions, RemoteError};
use super::transport::SessionId;
use crate::browser::transport::Transport;
use std::thread::sleep;
pub mod element;
mod keys;
pub mod point;
#[derive(Debug)]
pub enum RequestPausedDecision {
Fulfill(FulfillRequest),
Fail(FailRequest),
Continue(Option<ContinueRequest>),
}
#[rustfmt::skip]
pub type ResponseHandler = Box<
dyn Fn(
ResponseReceivedEventParams,
&dyn Fn() -> Result<
GetResponseBodyReturnObject,
Error,
>,
) + Send
+ Sync,
>;
type SyncSendEvent = dyn EventListener<Event> + Send + Sync;
pub trait RequestInterceptor {
fn intercept(
&self,
transport: Arc<Transport>,
session_id: SessionId,
event: RequestPausedEvent,
) -> RequestPausedDecision;
}
impl<F> RequestInterceptor for F
where
F: Fn(Arc<Transport>, SessionId, RequestPausedEvent) -> RequestPausedDecision + Send + Sync,
{
fn intercept(
&self,
transport: Arc<Transport>,
session_id: SessionId,
event: RequestPausedEvent,
) -> RequestPausedDecision {
self(transport, session_id, event)
}
}
type RequestIntercept = dyn RequestInterceptor + Send + Sync;
pub trait EventListener<T> {
fn on_event(&self, event: &T);
}
impl<T, F: Fn(&T) + Send + Sync> EventListener<T> for F {
fn on_event(&self, event: &T) {
self(event);
}
}
pub trait Binding {
fn call_binding(&self, data: Json);
}
impl<T: Fn(Json) + Send + Sync> Binding for T {
fn call_binding(&self, data: Json) {
self(data);
}
}
pub type SafeBinding = dyn Binding + Send + Sync;
pub type FunctionBinding = HashMap<String, Arc<SafeBinding>>;
pub struct Tab {
target_id: TargetID,
transport: Arc<Transport>,
session_id: SessionId,
navigating: Arc<AtomicBool>,
target_info: Arc<Mutex<TargetInfo>>,
request_interceptor: Arc<Mutex<Arc<RequestIntercept>>>,
response_handler: Arc<Mutex<HashMap<String, ResponseHandler>>>,
auth_handler: Arc<Mutex<AuthChallengeResponse>>,
default_timeout: Arc<RwLock<Duration>>,
page_bindings: Arc<Mutex<FunctionBinding>>,
event_listeners: Arc<Mutex<Vec<Arc<SyncSendEvent>>>>,
slow_motion_multiplier: Arc<RwLock<f64>>, }
#[derive(Debug, Error)]
#[error("No element found")]
pub struct NoElementFound {}
#[derive(Debug, Error)]
#[error("Navigate failed: {}", error_text)]
pub struct NavigationFailed {
error_text: String,
}
#[derive(Debug, Error)]
#[error("No LocalStorage item was found")]
pub struct NoLocalStorageItemFound {}
impl NoElementFound {
pub fn map(error: Error) -> Error {
match error.downcast::<RemoteError>() {
Ok(remote_error) => {
match remote_error.message.as_ref() {
"Could not find node with given id" => Self {}.into(),
_ => remote_error.into(),
}
}
Err(original_error) => original_error,
}
}
}
impl Tab {
pub fn new(target_info: TargetInfo, transport: Arc<Transport>) -> Result<Self> {
let target_id = target_info.target_id.clone();
let session_id = transport
.call_method_on_browser(AttachToTarget {
target_id: target_id.clone(),
flatten: None,
})?
.session_id
.into();
debug!("New tab attached with session ID: {:?}", session_id);
let target_info_mutex = Arc::new(Mutex::new(target_info));
let tab = Self {
target_id,
transport,
session_id,
navigating: Arc::new(AtomicBool::new(false)),
target_info: target_info_mutex,
page_bindings: Arc::new(Mutex::new(HashMap::new())),
request_interceptor: Arc::new(Mutex::new(Arc::new(
|_transport, _session_id, _interception| RequestPausedDecision::Continue(None),
))),
response_handler: Arc::new(Mutex::new(HashMap::new())),
auth_handler: Arc::new(Mutex::new(AuthChallengeResponse {
response: Fetch::AuthChallengeResponseResponse::Default,
username: None,
password: None,
})),
default_timeout: Arc::new(RwLock::new(Duration::from_secs(600))),
event_listeners: Arc::new(Mutex::new(Vec::new())),
slow_motion_multiplier: Arc::new(RwLock::new(0.0)),
};
tab.call_method(Page::Enable(None))?;
tab.call_method(Page::SetLifecycleEventsEnabled { enabled: true })?;
tab.start_event_handler_thread();
Ok(tab)
}
pub fn update_target_info(&self, target_info: TargetInfo) {
let mut info = self.target_info.lock().unwrap();
*info = target_info;
}
pub fn get_target_id(&self) -> &TargetID {
&self.target_id
}
pub fn get_target_info(&self) -> Result<TargetInfo> {
Ok(self
.call_method(Target::GetTargetInfo {
target_id: Some(self.get_target_id().to_string()),
})?
.target_info)
}
pub fn get_browser_context_id(&self) -> Result<Option<String>> {
Ok(self.get_target_info()?.browser_context_id)
}
pub fn get_url(&self) -> String {
let info = self.target_info.lock().unwrap();
info.url.clone()
}
pub fn set_user_agent(
&self,
user_agent: &str,
accept_language: Option<&str>,
platform: Option<&str>,
) -> Result<()> {
self.call_method(SetUserAgentOverride {
user_agent: user_agent.to_string(),
accept_language: accept_language.map(std::string::ToString::to_string),
platform: platform.map(std::string::ToString::to_string),
user_agent_metadata: None,
})
.map(|_| ())
}
fn start_event_handler_thread(&self) {
let transport: Arc<Transport> = Arc::clone(&self.transport);
let incoming_events_rx = self
.transport
.listen_to_target_events(self.session_id.clone());
let navigating = Arc::clone(&self.navigating);
let interceptor_mutex = Arc::clone(&self.request_interceptor);
let response_handler_mutex = self.response_handler.clone();
let auth_handler_mutex = self.auth_handler.clone();
let session_id = self.session_id.clone();
let listeners_mutex = Arc::clone(&self.event_listeners);
let bindings_mutex = Arc::clone(&self.page_bindings);
let received_event_params = Arc::new(Mutex::new(HashMap::new()));
thread::spawn(move || {
for event in incoming_events_rx {
let listeners = listeners_mutex.lock().unwrap();
listeners.iter().for_each(|listener| {
listener.on_event(&event);
});
match event {
Event::PageLifecycleEvent(lifecycle_event) => {
let event_name = lifecycle_event.params.name.as_ref();
trace!("Lifecycle event: {}", event_name);
match event_name {
"networkAlmostIdle" => {
navigating.store(false, Ordering::SeqCst);
}
"init" => {
navigating.store(true, Ordering::SeqCst);
}
_ => {}
}
}
Event::RuntimeBindingCalled(binding) => {
let bindings = bindings_mutex.lock().unwrap().clone();
let name = binding.params.name;
let payload = binding.params.payload;
let func = &Arc::clone(bindings.get(&name).unwrap());
func.call_binding(json!(payload));
}
Event::FetchRequestPaused(event) => {
let interceptor = interceptor_mutex.lock().unwrap();
let decision = interceptor.intercept(
Arc::clone(&transport),
session_id.clone(),
event.clone(),
);
let result = match decision {
RequestPausedDecision::Continue(continue_request) => {
if let Some(continue_request) = continue_request {
transport
.call_method_on_target(session_id.clone(), continue_request)
.map(|_| ())
} else {
transport
.call_method_on_target(
session_id.clone(),
ContinueRequest {
request_id: event.params.request_id,
url: None,
method: None,
post_data: None,
headers: None,
intercept_response: None,
},
)
.map(|_| ())
}
}
RequestPausedDecision::Fulfill(fulfill_request) => transport
.call_method_on_target(session_id.clone(), fulfill_request)
.map(|_| ()),
RequestPausedDecision::Fail(fail_request) => transport
.call_method_on_target(session_id.clone(), fail_request)
.map(|_| ()),
};
if result.is_err() {
warn!("Tried to handle request after connection was closed");
}
}
Event::FetchAuthRequired(event) => {
let auth_challenge_response = auth_handler_mutex.lock().unwrap().clone();
let request_id = event.params.request_id;
let method = ContinueWithAuth {
request_id,
auth_challenge_response,
};
let result = transport.call_method_on_target(session_id.clone(), method);
if result.is_err() {
warn!("Tried to handle request after connection was closed");
}
}
Event::NetworkResponseReceived(ev) => {
let request_id = ev.params.request_id.clone();
received_event_params
.lock()
.unwrap()
.insert(request_id, ev.params);
}
Event::NetworkLoadingFinished(ev) => {
response_handler_mutex.lock().unwrap().iter().for_each(
|(_name, handler)| {
let request_id = ev.params.request_id.clone();
let retrieve_body = || {
let method = GetResponseBody {
request_id: request_id.clone(),
};
transport.call_method_on_target(session_id.clone(), method)
};
if let Some(params) =
received_event_params.lock().unwrap().get(&request_id)
{
handler(params.clone(), &retrieve_body);
} else {
warn!("Request id does not exist");
}
},
);
}
_ => {
let mut raw_event = format!("{:?}", event);
raw_event.truncate(50);
trace!("Unhandled event: {}", raw_event);
}
}
}
info!("finished tab's event handling loop");
});
}
pub fn expose_function(&self, name: &str, func: Arc<SafeBinding>) -> Result<()> {
let bindings_mutex = Arc::clone(&self.page_bindings);
let mut bindings = bindings_mutex.lock().unwrap();
bindings.insert(name.to_string(), func);
let expression = r#"
(function addPageBinding(bindingName) {
const binding = window[bindingName];
window[bindingName] = (...args) => {
const me = window[bindingName];
let callbacks = me['callbacks'];
if (!callbacks) {
callbacks = new Map();
me['callbacks'] = callbacks;
}
const seq = (me['lastSeq'] || 0) + 1;
me['lastSeq'] = seq;
const promise = new Promise((resolve, reject) => callbacks.set(seq, {resolve, reject}));
binding(JSON.stringify({name: bindingName, seq, args}));
return promise;
};
})()
"#;
self.call_method(AddBinding {
name: name.to_string(),
execution_context_id: None,
execution_context_name: None,
})?;
self.call_method(AddScriptToEvaluateOnNewDocument {
source: expression.to_string(),
world_name: None,
include_command_line_api: None,
})?;
Ok(())
}
pub fn remove_function(&self, name: &str) -> Result<()> {
let bindings_mutex = Arc::clone(&self.page_bindings);
let mut bindings = bindings_mutex.lock().unwrap();
bindings.remove(name).unwrap();
Ok(())
}
pub fn call_method<C>(&self, method: C) -> Result<C::ReturnObject>
where
C: Method + serde::Serialize + std::fmt::Debug,
{
trace!("Calling method: {:?}", method);
let result = self
.transport
.call_method_on_target(self.session_id.clone(), method);
let mut result_string = format!("{:?}", result);
result_string.truncate(70);
trace!("Got result: {:?}", result_string);
result
}
pub fn wait_until_navigated(&self) -> Result<&Self> {
let navigating = Arc::clone(&self.navigating);
let timeout = *self.default_timeout.read().unwrap();
util::Wait::with_timeout(timeout).until(|| {
if navigating.load(Ordering::SeqCst) {
None
} else {
Some(true)
}
})?;
debug!("A tab finished navigating");
Ok(self)
}
pub fn bring_to_front(&self) -> Result<Page::BringToFrontReturnObject> {
self.call_method(Page::BringToFront(None))
}
pub fn navigate_to(&self, url: &str) -> Result<&Self> {
let return_object = self.call_method(Navigate {
url: url.to_string(),
referrer: None,
transition_Type: None,
frame_id: None,
referrer_policy: None,
})?;
if let Some(error_text) = return_object.error_text {
return Err(NavigationFailed { error_text }.into());
}
let navigating = Arc::clone(&self.navigating);
navigating.store(true, Ordering::SeqCst);
info!("Navigating a tab to {}", url);
Ok(self)
}
pub fn set_default_timeout(&self, timeout: Duration) -> &Self {
let mut current_timeout = self.default_timeout.write().unwrap();
*current_timeout = timeout;
self
}
pub fn set_slow_motion_multiplier(&self, multiplier: f64) -> &Self {
let mut slow_motion_multiplier = self.slow_motion_multiplier.write().unwrap();
*slow_motion_multiplier = multiplier;
self
}
fn optional_slow_motion_sleep(&self, millis: u64) {
let multiplier = self.slow_motion_multiplier.read().unwrap();
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let scaled_millis = millis * *multiplier as u64;
sleep(Duration::from_millis(scaled_millis));
}
pub fn wait_for_element(&self, selector: &str) -> Result<Element<'_>> {
self.wait_for_element_with_custom_timeout(selector, *self.default_timeout.read().unwrap())
}
pub fn wait_for_xpath(&self, selector: &str) -> Result<Element<'_>> {
self.wait_for_xpath_with_custom_timeout(selector, *self.default_timeout.read().unwrap())
}
pub fn wait_for_element_with_custom_timeout(
&self,
selector: &str,
timeout: std::time::Duration,
) -> Result<Element<'_>> {
debug!("Waiting for element with selector: {:?}", selector);
util::Wait::with_timeout(timeout).strict_until(
|| self.find_element(selector),
Error::downcast::<NoElementFound>,
)
}
pub fn wait_for_xpath_with_custom_timeout(
&self,
selector: &str,
timeout: std::time::Duration,
) -> Result<Element<'_>> {
debug!("Waiting for element with selector: {:?}", selector);
util::Wait::with_timeout(timeout).strict_until(
|| self.find_element_by_xpath(selector),
Error::downcast::<NoElementFound>,
)
}
pub fn wait_for_elements(&self, selector: &str) -> Result<Vec<Element<'_>>> {
debug!("Waiting for element with selector: {:?}", selector);
util::Wait::with_timeout(*self.default_timeout.read().unwrap()).strict_until(
|| self.find_elements(selector),
Error::downcast::<NoElementFound>,
)
}
pub fn wait_for_elements_by_xpath(&self, selector: &str) -> Result<Vec<Element<'_>>> {
debug!("Waiting for element with selector: {:?}", selector);
util::Wait::with_timeout(*self.default_timeout.read().unwrap()).strict_until(
|| self.find_elements_by_xpath(selector),
Error::downcast::<NoElementFound>,
)
}
pub fn find_element(&self, selector: &str) -> Result<Element<'_>> {
let root_node_id = self.get_document()?.node_id;
trace!("Looking up element via selector: {}", selector);
self.run_query_selector_on_node(root_node_id, selector)
}
pub fn find_element_by_xpath(&self, query: &str) -> Result<Element<'_>> {
self.get_document()?;
self.call_method(DOM::PerformSearch {
query: query.to_string(),
include_user_agent_shadow_dom: None,
})
.and_then(|o| {
Ok(self
.call_method(DOM::GetSearchResults {
search_id: o.search_id,
from_index: 0,
to_index: o.result_count,
})?
.node_ids[0])
})
.and_then(|id| {
if id == 0 {
Err(NoElementFound {}.into())
} else {
Ok(Element::new(self, id)?)
}
})
}
pub fn run_query_selector_on_node(
&self,
node_id: NodeId,
selector: &str,
) -> Result<Element<'_>> {
let node_id = self
.call_method(DOM::QuerySelector {
node_id,
selector: selector.to_string(),
})
.map_err(NoElementFound::map)?
.node_id;
Element::new(self, node_id)
}
pub fn run_query_selector_all_on_node(
&self,
node_id: NodeId,
selector: &str,
) -> Result<Vec<Element<'_>>> {
let node_ids = self
.call_method(DOM::QuerySelectorAll {
node_id,
selector: selector.to_string(),
})
.map_err(NoElementFound::map)?
.node_ids;
node_ids
.iter()
.map(|node_id| Element::new(self, *node_id))
.collect()
}
pub fn get_document(&self) -> Result<Node> {
Ok(self
.call_method(DOM::GetDocument {
depth: Some(0),
pierce: Some(false),
})?
.root)
}
pub fn get_content(&self) -> Result<String> {
let func = "
(function () {
let retVal = '';
if (document.doctype)
retVal = new XMLSerializer().serializeToString(document.doctype);
if (document.documentElement)
retVal += document.documentElement.outerHTML;
return retVal;
})();";
let html = self.evaluate(func, false)?.value.unwrap();
Ok(String::from(html.as_str().unwrap()))
}
pub fn find_elements(&self, selector: &str) -> Result<Vec<Element<'_>>> {
trace!("Looking up elements via selector: {}", selector);
let root_node_id = self.get_document()?.node_id;
let node_ids = self
.call_method(DOM::QuerySelectorAll {
node_id: root_node_id,
selector: selector.to_string(),
})
.map_err(NoElementFound::map)?
.node_ids;
if node_ids.is_empty() {
return Err(NoElementFound {}.into());
}
node_ids
.into_iter()
.map(|node_id| Element::new(self, node_id))
.collect()
}
pub fn find_elements_by_xpath(&self, query: &str) -> Result<Vec<Element<'_>>> {
self.call_method(DOM::PerformSearch {
query: query.to_string(),
include_user_agent_shadow_dom: None,
})
.and_then(|o| {
Ok(self
.call_method(DOM::GetSearchResults {
search_id: o.search_id,
from_index: 0,
to_index: o.result_count,
})?
.node_ids)
})
.and_then(|ids| {
ids.iter()
.filter(|id| **id != 0)
.map(|id| Element::new(self, *id))
.collect()
})
}
pub fn describe_node(&self, node_id: NodeId) -> Result<Node> {
let node = self
.call_method(DOM::DescribeNode {
node_id: Some(node_id),
backend_node_id: None,
depth: Some(100),
object_id: None,
pierce: None,
})?
.node;
Ok(node)
}
pub fn type_str(&self, string_to_type: &str) -> Result<&Self> {
for c in string_to_type.split("") {
if c.is_empty() {
continue;
}
let definition = keys::get_key_definition(c);
match definition {
Ok(key) => {
let v: DispatchKeyEvent = key.into();
self.call_method(v.clone())?;
self.call_method(DispatchKeyEvent {
Type: Input::DispatchKeyEventTypeOption::KeyUp,
..v
})?;
}
Err(_) => {
self.send_character(c)?;
}
}
}
Ok(self)
}
pub fn send_character(&self, char_to_send: &str) -> Result<&Self> {
self.call_method(Input::InsertText {
text: char_to_send.to_string(),
})?;
Ok(self)
}
pub fn press_key(&self, key: &str) -> Result<&Self> {
let definiton = keys::get_key_definition(key)?;
let text = definiton
.text
.or({
if definiton.key.len() == 1 {
Some(definiton.key)
} else {
None
}
})
.map(std::string::ToString::to_string);
let key_down_event_type = if text.is_some() {
Input::DispatchKeyEventTypeOption::KeyDown
} else {
Input::DispatchKeyEventTypeOption::RawKeyDown
};
let key = Some(definiton.key.to_string());
let code = Some(definiton.code.to_string());
self.optional_slow_motion_sleep(25);
self.call_method(Input::DispatchKeyEvent {
Type: key_down_event_type,
key: key.clone(),
text: text.clone(),
code: code.clone(),
windows_virtual_key_code: Some(definiton.key_code),
native_virtual_key_code: Some(definiton.key_code),
modifiers: None,
timestamp: None,
unmodified_text: None,
key_identifier: None,
auto_repeat: None,
is_keypad: None,
is_system_key: None,
location: None,
commands: None,
})?;
self.call_method(Input::DispatchKeyEvent {
Type: Input::DispatchKeyEventTypeOption::KeyUp,
key,
text,
code,
windows_virtual_key_code: Some(definiton.key_code),
native_virtual_key_code: Some(definiton.key_code),
modifiers: None,
timestamp: None,
unmodified_text: None,
key_identifier: None,
auto_repeat: None,
is_keypad: None,
is_system_key: None,
location: None,
commands: None,
})?;
Ok(self)
}
pub fn move_mouse_to_point(&self, point: Point) -> Result<&Self> {
if point.x == 0.0 && point.y == 0.0 {
warn!("Midpoint of element shouldn't be 0,0. Something is probably wrong.");
}
self.optional_slow_motion_sleep(100);
self.call_method(Input::DispatchMouseEvent {
Type: Input::DispatchMouseEventTypeOption::MouseMoved,
x: point.x,
y: point.y,
modifiers: None,
timestamp: None,
button: None,
buttons: None,
click_count: None,
force: None,
tangential_pressure: None,
tilt_x: None,
tilt_y: None,
twist: None,
delta_x: None,
delta_y: None,
pointer_Type: None,
})?;
Ok(self)
}
pub fn click_point(&self, point: Point) -> Result<&Self> {
trace!("Clicking point: {:?}", point);
if point.x == 0.0 && point.y == 0.0 {
warn!("Midpoint of element shouldn't be 0,0. Something is probably wrong.");
}
self.move_mouse_to_point(point)?;
self.optional_slow_motion_sleep(250);
self.call_method(Input::DispatchMouseEvent {
Type: Input::DispatchMouseEventTypeOption::MousePressed,
x: point.x,
y: point.y,
button: Some(Input::MouseButton::Left),
click_count: Some(1),
modifiers: None,
timestamp: None,
buttons: None,
force: None,
tangential_pressure: None,
tilt_x: None,
tilt_y: None,
twist: None,
delta_x: None,
delta_y: None,
pointer_Type: None,
})?;
self.call_method(Input::DispatchMouseEvent {
Type: Input::DispatchMouseEventTypeOption::MouseReleased,
x: point.x,
y: point.y,
button: Some(Input::MouseButton::Left),
click_count: Some(1),
modifiers: None,
timestamp: None,
buttons: None,
force: None,
tangential_pressure: None,
tilt_x: None,
tilt_y: None,
twist: None,
delta_x: None,
delta_y: None,
pointer_Type: None,
})?;
Ok(self)
}
pub fn capture_screenshot(
&self,
format: Page::CaptureScreenshotFormatOption,
quality: Option<u32>,
clip: Option<Page::Viewport>,
from_surface: bool,
) -> Result<Vec<u8>> {
let data = self
.call_method(Page::CaptureScreenshot {
format: Some(format),
clip,
quality,
from_surface: Some(from_surface),
capture_beyond_viewport: None,
})?
.data;
base64::decode(&data).map_err(Into::into)
}
pub fn print_to_pdf(&self, options: Option<PrintToPdfOptions>) -> Result<Vec<u8>> {
if let Some(options) = options {
let transfer_mode: Option<Page::PrintToPDFTransfer_modeOption> =
options.transfer_mode.and_then(std::convert::Into::into);
let data = self
.call_method(Page::PrintToPDF {
landscape: options.landscape,
display_header_footer: options.display_header_footer,
print_background: options.print_background,
scale: options.scale,
paper_width: options.paper_width,
paper_height: options.paper_height,
margin_top: options.margin_top,
margin_bottom: options.margin_bottom,
margin_left: options.margin_left,
margin_right: options.margin_right,
page_ranges: options.page_ranges,
ignore_invalid_page_ranges: options.ignore_invalid_page_ranges,
header_template: options.header_template,
footer_template: options.footer_template,
prefer_css_page_size: options.prefer_css_page_size,
transfer_mode,
})?
.data;
base64::decode(&data).map_err(Into::into)
} else {
let data = self
.call_method(Page::PrintToPDF {
..Default::default()
})?
.data;
base64::decode(&data).map_err(Into::into)
}
}
pub fn reload(
&self,
ignore_cache: bool,
script_to_evaluate_on_load: Option<&str>,
) -> Result<&Self> {
self.optional_slow_motion_sleep(100);
self.call_method(Page::Reload {
ignore_cache: Some(ignore_cache),
script_to_evaluate_on_load: script_to_evaluate_on_load
.map(std::string::ToString::to_string),
})?;
Ok(self)
}
pub fn set_transparent_background_color(&self) -> Result<&Self> {
self.call_method(Emulation::SetDefaultBackgroundColorOverride {
color: Some(DOM::RGBA {
r: 0,
g: 0,
b: 0,
a: Some(0.0),
}),
})?;
Ok(self)
}
pub fn set_background_color(&self, color: DOM::RGBA) -> Result<&Self> {
self.call_method(Emulation::SetDefaultBackgroundColorOverride { color: Some(color) })?;
Ok(self)
}
pub fn enable_profiler(&self) -> Result<&Self> {
self.call_method(Profiler::Enable(None))?;
Ok(self)
}
pub fn disable_profiler(&self) -> Result<&Self> {
self.call_method(Profiler::Disable(None))?;
Ok(self)
}
pub fn start_js_coverage(&self) -> Result<&Self> {
self.call_method(Profiler::StartPreciseCoverage {
call_count: Some(true),
detailed: Some(true),
allow_triggered_updates: None,
})?;
Ok(self)
}
pub fn stop_js_coverage(&self) -> Result<&Self> {
self.call_method(Profiler::StopPreciseCoverage(None))?;
Ok(self)
}
pub fn take_precise_js_coverage(&self) -> Result<Vec<Profiler::ScriptCoverage>> {
let script_coverages = self
.call_method(Profiler::TakePreciseCoverage(None))?
.result;
Ok(script_coverages)
}
pub fn enable_fetch(
&self,
patterns: Option<&[Fetch::RequestPattern]>,
handle_auth_requests: Option<bool>,
) -> Result<&Self> {
self.call_method(Fetch::Enable {
patterns: patterns.map(Vec::from),
handle_auth_requests,
})?;
Ok(self)
}
pub fn disable_fetch(&self) -> Result<&Self> {
self.call_method(Fetch::Disable(None))?;
Ok(self)
}
pub fn enable_request_interception(&self, interceptor: Arc<RequestIntercept>) -> Result<()> {
let mut current_interceptor = self.request_interceptor.lock().unwrap();
*current_interceptor = interceptor;
Ok(())
}
pub fn authenticate(
&self,
username: Option<String>,
password: Option<String>,
) -> Result<&Self> {
let mut current_auth_handler = self.auth_handler.lock().unwrap();
*current_auth_handler = AuthChallengeResponse {
response: Fetch::AuthChallengeResponseResponse::ProvideCredentials,
username,
password,
};
Ok(self)
}
pub fn register_response_handling<S: ToString>(
&self,
handler_name: S,
handler: ResponseHandler,
) -> Result<Option<ResponseHandler>> {
self.call_method(Network::Enable {
max_total_buffer_size: None,
max_resource_buffer_size: None,
max_post_data_size: None,
})?;
Ok(self
.response_handler
.lock()
.unwrap()
.insert(handler_name.to_string(), handler))
}
pub fn deregister_response_handling(
&self,
handler_name: &str,
) -> Result<Option<ResponseHandler>> {
Ok(self.response_handler.lock().unwrap().remove(handler_name))
}
pub fn deregister_response_handling_all(&self) -> Result<()> {
self.response_handler.lock().unwrap().clear();
Ok(())
}
pub fn enable_runtime(&self) -> Result<&Self> {
self.call_method(Runtime::Enable(None))?;
Ok(self)
}
pub fn disable_runtime(&self) -> Result<&Self> {
self.call_method(Runtime::Disable(None))?;
Ok(self)
}
pub fn enable_debugger(&self) -> Result<()> {
self.call_method(Debugger::Enable {
max_scripts_cache_size: None,
})?;
Ok(())
}
pub fn disable_debugger(&self) -> Result<()> {
self.call_method(Debugger::Disable(None))?;
Ok(())
}
pub fn get_script_source(&self, script_id: &str) -> Result<String> {
Ok(self
.call_method(Debugger::GetScriptSource {
script_id: script_id.to_string(),
})?
.script_source)
}
pub fn enable_log(&self) -> Result<&Self> {
self.call_method(Log::Enable(None))?;
Ok(self)
}
pub fn disable_log(&self) -> Result<&Self> {
self.call_method(Log::Disable(None))?;
Ok(self)
}
pub fn start_violations_report(&self, config: Vec<ViolationSetting>) -> Result<&Self> {
self.call_method(Log::StartViolationsReport { config })?;
Ok(self)
}
pub fn stop_violations_report(&self) -> Result<&Self> {
self.call_method(Log::StopViolationsReport(None))?;
Ok(self)
}
pub fn evaluate(&self, expression: &str, await_promise: bool) -> Result<Runtime::RemoteObject> {
let result = self
.call_method(Runtime::Evaluate {
expression: expression.to_string(),
return_by_value: Some(false),
generate_preview: Some(true),
silent: Some(false),
await_promise: Some(await_promise),
include_command_line_api: Some(false),
user_gesture: Some(false),
object_group: None,
context_id: None,
throw_on_side_effect: None,
timeout: None,
disable_breaks: None,
repl_mode: None,
allow_unsafe_eval_blocked_by_csp: None,
unique_context_id: None,
})?
.result;
Ok(result)
}
pub fn add_event_listener(&self, listener: Arc<SyncSendEvent>) -> Result<Weak<SyncSendEvent>> {
let mut listeners = self.event_listeners.lock().unwrap();
listeners.push(listener);
Ok(Arc::downgrade(listeners.last().unwrap()))
}
pub fn remove_event_listener(&self, listener: &Weak<SyncSendEvent>) -> Result<()> {
let listener = listener.upgrade();
if listener.is_none() {
return Ok(());
}
let listener = listener.unwrap();
let mut listeners = self.event_listeners.lock().unwrap();
let pos = listeners.iter().position(|x| Arc::ptr_eq(x, &listener));
if let Some(idx) = pos {
listeners.remove(idx);
}
Ok(())
}
pub fn close_target(&self) -> Result<bool> {
self.call_method(Target::CloseTarget {
target_id: self.get_target_id().to_string(),
})
.map(|r| r.success)
}
pub fn close_with_unload(&self) -> Result<bool> {
self.call_method(Page::Close(None)).map(|_| true)
}
pub fn close(&self, fire_unload: bool) -> Result<bool> {
self.optional_slow_motion_sleep(50);
if fire_unload {
return self.close_with_unload();
}
self.close_target()
}
pub fn activate(&self) -> Result<&Self> {
self.call_method(Target::ActivateTarget {
target_id: self.get_target_id().clone(),
})
.map(|_| self)
}
pub fn get_bounds(&self) -> Result<CurrentBounds, Error> {
self.transport
.call_method_on_browser(Browser::GetWindowForTarget {
target_id: Some(self.get_target_id().to_string()),
})
.map(|r| r.bounds.into())
}
pub fn set_bounds(&self, bounds: Bounds) -> Result<&Self, Error> {
let window_id = self
.transport
.call_method_on_browser(Browser::GetWindowForTarget {
target_id: Some(self.get_target_id().to_string()),
})?
.window_id;
if let Bounds::Normal { .. } = &bounds {
self.transport
.call_method_on_browser(Browser::SetWindowBounds {
window_id,
bounds: Browser::Bounds {
left: None,
top: None,
width: None,
height: None,
window_state: Some(Browser::WindowState::Normal),
},
})?;
}
self.transport
.call_method_on_browser(Browser::SetWindowBounds {
window_id,
bounds: bounds.into(),
})?;
Ok(self)
}
pub fn get_cookies(&self) -> Result<Vec<Cookie>> {
Ok(self
.call_method(Network::GetCookies { urls: None })?
.cookies)
}
pub fn set_cookies(&self, cs: Vec<Network::CookieParam>) -> Result<()> {
use Network::SetCookies;
let url = self.get_url();
let starts_with_http = url.starts_with("http");
let cookies: Vec<Network::CookieParam> = cs
.into_iter()
.map(|c| {
if c.url.is_none() && starts_with_http {
Network::CookieParam {
url: Some(url.clone()),
..c
}
} else {
c
}
})
.collect();
self.delete_cookies(
cookies
.clone()
.into_iter()
.map(std::convert::Into::into)
.collect(),
)?;
self.call_method(SetCookies { cookies })?;
Ok(())
}
pub fn delete_cookies(&self, cs: Vec<Network::DeleteCookies>) -> Result<()> {
let url = self.get_url();
let starts_with_http = url.starts_with("http");
cs.into_iter()
.map(|c| {
if c.url.is_none() && starts_with_http {
Network::DeleteCookies {
url: Some(url.clone()),
..c
}
} else {
c
}
})
.try_for_each(|c| -> Result<(), anyhow::Error> {
let _ = self.call_method(c)?;
Ok(())
})?;
Ok(())
}
pub fn get_title(&self) -> Result<String> {
let remote_object = self.evaluate("document.title", false)?;
Ok(serde_json::from_value(remote_object.value.unwrap())?)
}
pub fn set_file_chooser_dialog_interception(&self, enabled: bool) -> Result<()> {
self.call_method(SetInterceptFileChooserDialog { enabled })?;
Ok(())
}
pub fn handle_file_chooser(&self, files: Vec<String>, node_id: u32) -> Result<()> {
self.call_method(DOM::SetFileInputFiles {
files,
node_id: Some(node_id),
backend_node_id: None,
object_id: None,
})?;
Ok(())
}
pub fn set_extra_http_headers(&self, headers: HashMap<&str, &str>) -> Result<()> {
self.call_method(Network::Enable {
max_total_buffer_size: None,
max_resource_buffer_size: None,
max_post_data_size: None,
})?;
self.call_method(SetExtraHTTPHeaders {
headers: Network::Headers(Some(json!(headers))),
})?;
Ok(())
}
pub fn set_storage<T>(&self, item_name: &str, item: T) -> Result<()>
where
T: Serialize,
{
let value = json!(item).to_string();
self.evaluate(
&format!(
r#"localStorage.setItem("{}",JSON.stringify({}))"#,
item_name, value
),
false,
)?;
Ok(())
}
pub fn get_storage<T>(&self, item_name: &str) -> Result<T>
where
T: DeserializeOwned,
{
let object = self.evaluate(&format!(r#"localStorage.getItem("{}")"#, item_name), false)?;
let json: Option<T> = object.value.and_then(|v| match v {
serde_json::Value::String(ref s) => {
let result = serde_json::from_str(s);
if let Ok(r) = result {
Some(r)
} else {
Some(serde_json::from_value(v).unwrap())
}
}
_ => None,
});
match json {
Some(v) => Ok(v),
None => Err(NoLocalStorageItemFound {}.into()),
}
}
pub fn remove_storage(&self, item_name: &str) -> Result<()> {
self.evaluate(
&format!(r#"localStorage.removeItem("{}")"#, item_name),
false,
)?;
Ok(())
}
pub fn stop_loading(&self) -> Result<bool> {
self.call_method(Page::StopLoading(None)).map(|_| true)
}
}