#![crate_name = "webdriver_server"]
#![crate_type = "rlib"]
#![deny(unsafe_code)]
mod actions;
mod capabilities;
mod script_argument_extraction;
mod server;
mod session;
mod timeout;
mod user_prompt;
use std::borrow::ToOwned;
use std::cell::LazyCell;
use std::collections::{BTreeMap, HashMap};
use std::io::Cursor;
use std::net::{SocketAddr, SocketAddrV4};
use std::thread::sleep;
use std::time::{Duration, Instant};
use std::{env, fmt, process, thread};
use base64::Engine;
use capabilities::ServoCapabilities;
use cookie::{CookieBuilder, Expiration, SameSite};
use crossbeam_channel::{Receiver, RecvTimeoutError, Sender, after, select, unbounded};
use embedder_traits::{
CustomHandlersAutomationMode, EventLoopWaker, ImeEvent, InputEvent, JSValue,
JavaScriptEvaluationError, JavaScriptEvaluationResultSerializationError, NewWindowTypeHint,
WebDriverCommandMsg, WebDriverFrameId, WebDriverJSResult, WebDriverLoadStatus,
WebDriverScriptCommand,
};
use euclid::{Point2D, Rect, Size2D};
use http::method::Method;
use image::{DynamicImage, ImageFormat};
use keyboard_types::webdriver::{Event as DispatchStringEvent, KeyInputState, send_keys};
use keyboard_types::{Code, Key, KeyState, KeyboardEvent, Location, NamedKey};
use log::{debug, error, info};
use serde::de::{Deserializer, MapAccess, Visitor};
use serde::ser::Serializer;
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
use server::{Session, SessionTeardownKind, WebDriverHandler};
use servo_base::generic_channel::{self, GenericReceiver, GenericSender, RoutedReceiver};
use servo_base::id::{BrowsingContextId, WebViewId};
use servo_config::prefs::{self, PrefValue, Preferences};
use servo_geometry::DeviceIndependentIntRect;
use servo_url::ServoUrl;
use style_traits::CSSPixel;
use time::OffsetDateTime;
use uuid::Uuid;
use webdriver::actions::{
ActionSequence, ActionsType, KeyAction, KeyActionItem, KeyDownAction, KeyUpAction,
PointerAction, PointerActionItem, PointerActionParameters, PointerDownAction,
PointerMoveAction, PointerOrigin, PointerType, PointerUpAction,
};
use webdriver::capabilities::CapabilitiesMatching;
use webdriver::command::{
ActionsParameters, AddCookieParameters, GetParameters, JavascriptCommandParameters,
LocatorParameters, NewSessionParameters, NewWindowParameters, SendKeysParameters,
SwitchToFrameParameters, SwitchToWindowParameters, TimeoutsParameters, WebDriverCommand,
WebDriverExtensionCommand, WebDriverMessage, WindowRectParameters,
};
use webdriver::common::{
Cookie, Date, LocatorStrategy, Parameters, ShadowRoot, WebElement, WebFrame, WebWindow,
};
use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult};
use webdriver::httpapi::WebDriverExtensionRoute;
use webdriver::response::{
CloseWindowResponse, CookieResponse, CookiesResponse, ElementRectResponse, NewSessionResponse,
NewWindowResponse, TimeoutsResponse, ValueResponse, WebDriverResponse, WindowRectResponse,
};
use crate::actions::{ELEMENT_CLICK_BUTTON, InputSourceState, PendingActions, PointerInputState};
use crate::session::{PageLoadStrategy, WebDriverSession};
use crate::timeout::{DEFAULT_IMPLICIT_WAIT, DEFAULT_PAGE_LOAD_TIMEOUT, SCREENSHOT_TIMEOUT};
pub(crate) static MAXIMUM_SAFE_INTEGER: u64 = 9_007_199_254_740_991;
fn extension_routes() -> Vec<(Method, &'static str, ServoExtensionRoute)> {
vec![
(
Method::GET,
"/session/{sessionId}/servo/prefs/get",
ServoExtensionRoute::GetPrefs,
),
(
Method::POST,
"/session/{sessionId}/servo/prefs/set",
ServoExtensionRoute::SetPrefs,
),
(
Method::POST,
"/session/{sessionId}/servo/prefs/reset",
ServoExtensionRoute::ResetPrefs,
),
(
Method::DELETE,
"/session/{sessionId}/servo/shutdown",
ServoExtensionRoute::Shutdown,
),
(
Method::POST,
"/session/{sessionId}/custom-handlers/set-mode",
ServoExtensionRoute::CustomHandlersSetMode,
),
(
Method::POST,
"/session/{sessionId}/servo/cookies/reset",
ServoExtensionRoute::ResetAllCookies,
),
]
}
fn cookie_msg_to_cookie(cookie: cookie::Cookie) -> Cookie {
Cookie {
name: cookie.name().to_owned(),
value: cookie.value().to_owned(),
path: cookie.path().map(|s| s.to_owned()),
domain: cookie.domain().map(|s| s.to_owned()),
expiry: cookie.expires().and_then(|expiration| match expiration {
Expiration::DateTime(date_time) => Some(Date(date_time.unix_timestamp() as u64)),
Expiration::Session => None,
}),
secure: cookie.secure().unwrap_or(false),
http_only: cookie.http_only().unwrap_or(false),
same_site: cookie.same_site().map(|s| s.to_string()),
}
}
pub fn start_server(
port: u16,
embedder_sender: Sender<WebDriverCommandMsg>,
event_loop_waker: Box<dyn EventLoopWaker>,
default_preferences: Preferences,
) {
let handler = Handler::new(embedder_sender, event_loop_waker, default_preferences);
thread::Builder::new()
.name("WebDriverHttpServer".to_owned())
.spawn(move || {
let address = SocketAddrV4::new("0.0.0.0".parse().unwrap(), port);
match server::start(
SocketAddr::V4(address),
vec![],
vec![],
handler,
extension_routes(),
) {
Ok(listening) => info!("WebDriver server listening on {}", listening.socket),
Err(e) => panic!("Unable to start WebDriver HTTP server {e:?}"),
}
})
.expect("Thread spawning failed");
}
struct Handler {
load_status_receiver: RoutedReceiver<WebDriverLoadStatus>,
load_status_sender: GenericSender<WebDriverLoadStatus>,
session: Option<WebDriverSession>,
embedder_sender: Sender<WebDriverCommandMsg>,
event_loop_waker: Box<dyn EventLoopWaker>,
pending_input_event_receivers: Vec<Receiver<()>>,
pending_actions: Vec<PendingActions>,
default_preferences: Preferences,
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum ServoExtensionRoute {
GetPrefs,
SetPrefs,
ResetPrefs,
Shutdown,
CustomHandlersSetMode,
ResetAllCookies,
}
impl WebDriverExtensionRoute for ServoExtensionRoute {
type Command = ServoExtensionCommand;
fn command(
&self,
_parameters: &Parameters,
body_data: &Value,
) -> WebDriverResult<WebDriverCommand<ServoExtensionCommand>> {
let command = match *self {
ServoExtensionRoute::GetPrefs => {
let parameters: GetPrefsParameters = serde_json::from_value(body_data.clone())?;
ServoExtensionCommand::GetPrefs(parameters)
},
ServoExtensionRoute::SetPrefs => {
let parameters: SetPrefsParameters = serde_json::from_value(body_data.clone())?;
ServoExtensionCommand::SetPrefs(parameters)
},
ServoExtensionRoute::ResetPrefs => {
let parameters: GetPrefsParameters = serde_json::from_value(body_data.clone())?;
ServoExtensionCommand::ResetPrefs(parameters)
},
ServoExtensionRoute::CustomHandlersSetMode => {
let parameters: CustomHandlersSetModeParameters =
serde_json::from_value(body_data.clone())?;
ServoExtensionCommand::CustomHandlersSetMode(parameters)
},
ServoExtensionRoute::Shutdown => ServoExtensionCommand::Shutdown,
ServoExtensionRoute::ResetAllCookies => ServoExtensionCommand::ResetAllCookies,
};
Ok(WebDriverCommand::Extension(command))
}
}
#[derive(Clone, Debug)]
enum ServoExtensionCommand {
GetPrefs(GetPrefsParameters),
SetPrefs(SetPrefsParameters),
ResetPrefs(GetPrefsParameters),
CustomHandlersSetMode(CustomHandlersSetModeParameters),
Shutdown,
ResetAllCookies,
}
impl WebDriverExtensionCommand for ServoExtensionCommand {
fn parameters_json(&self) -> Option<Value> {
match *self {
ServoExtensionCommand::GetPrefs(ref x) => serde_json::to_value(x).ok(),
ServoExtensionCommand::SetPrefs(ref x) => serde_json::to_value(x).ok(),
ServoExtensionCommand::ResetPrefs(ref x) => serde_json::to_value(x).ok(),
ServoExtensionCommand::CustomHandlersSetMode(ref x) => serde_json::to_value(x).ok(),
ServoExtensionCommand::Shutdown | ServoExtensionCommand::ResetAllCookies => None,
}
}
}
#[derive(Clone)]
struct SendableJSValue(JSValue);
impl Serialize for SendableJSValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self.0 {
JSValue::Undefined | JSValue::Null => serializer.serialize_unit(),
JSValue::Boolean(x) => serializer.serialize_bool(x),
JSValue::Number(x) => {
if x.fract() == 0.0 {
serializer.serialize_i64(x as i64)
} else {
serializer.serialize_f64(x)
}
},
JSValue::String(ref x) => serializer.serialize_str(x),
JSValue::Element(ref x) => WebElement(x.clone()).serialize(serializer),
JSValue::ShadowRoot(ref x) => ShadowRoot(x.clone()).serialize(serializer),
JSValue::Frame(ref x) => WebFrame(x.clone()).serialize(serializer),
JSValue::Window(ref x) => WebWindow(x.clone()).serialize(serializer),
JSValue::Array(ref x) => x
.iter()
.map(|element| SendableJSValue(element.clone()))
.collect::<Vec<SendableJSValue>>()
.serialize(serializer),
JSValue::Object(ref x) => x
.iter()
.map(|(k, v)| (k.clone(), SendableJSValue(v.clone())))
.collect::<HashMap<String, SendableJSValue>>()
.serialize(serializer),
}
}
}
#[derive(Clone, Debug, PartialEq)]
struct WebDriverPrefValue(PrefValue);
impl Serialize for WebDriverPrefValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self.0 {
PrefValue::Bool(b) => serializer.serialize_bool(b),
PrefValue::Str(ref s) => serializer.serialize_str(s),
PrefValue::Float(f) => serializer.serialize_f64(f),
PrefValue::Int(i) => serializer.serialize_i64(i),
PrefValue::Array(ref v) => v
.iter()
.map(|value| WebDriverPrefValue(value.clone()))
.collect::<Vec<WebDriverPrefValue>>()
.serialize(serializer),
PrefValue::UInt(u) => serializer.serialize_u64(u),
}
}
}
impl<'de> Deserialize<'de> for WebDriverPrefValue {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct Visitor;
impl ::serde::de::Visitor<'_> for Visitor {
type Value = WebDriverPrefValue;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("preference value")
}
fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E>
where
E: ::serde::de::Error,
{
Ok(WebDriverPrefValue(PrefValue::Float(value)))
}
fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
where
E: ::serde::de::Error,
{
Ok(WebDriverPrefValue(PrefValue::Int(value)))
}
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: ::serde::de::Error,
{
Ok(WebDriverPrefValue(PrefValue::Int(value as i64)))
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: ::serde::de::Error,
{
Ok(WebDriverPrefValue(PrefValue::Str(String::from(value))))
}
fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E>
where
E: ::serde::de::Error,
{
Ok(WebDriverPrefValue(PrefValue::Bool(value)))
}
}
deserializer.deserialize_any(Visitor)
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
struct GetPrefsParameters {
prefs: Vec<String>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
struct SetPrefsParameters {
#[serde(deserialize_with = "map_to_vec")]
prefs: Vec<(String, WebDriverPrefValue)>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
struct CustomHandlersSetModeParameters {
mode: String,
}
fn map_to_vec<'de, D>(de: D) -> Result<Vec<(String, WebDriverPrefValue)>, D::Error>
where
D: Deserializer<'de>,
{
de.deserialize_map(TupleVecMapVisitor)
}
struct TupleVecMapVisitor;
impl<'de> Visitor<'de> for TupleVecMapVisitor {
type Value = Vec<(String, WebDriverPrefValue)>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a map")
}
#[inline]
fn visit_unit<E>(self) -> Result<Self::Value, E> {
Ok(Vec::new())
}
#[inline]
fn visit_map<T>(self, mut access: T) -> Result<Self::Value, T::Error>
where
T: MapAccess<'de>,
{
let mut values = Vec::new();
while let Some((key, value)) = access.next_entry()? {
values.push((key, value));
}
Ok(values)
}
}
enum VerifyBrowsingContextIsOpen {
Yes,
No,
}
enum ImplicitWait {
Return,
#[expect(dead_code, reason = "This will be used in the next patch")]
Continue,
}
impl From<ImplicitWait> for bool {
fn from(implicit_wait: ImplicitWait) -> Self {
match implicit_wait {
ImplicitWait::Return => true,
ImplicitWait::Continue => false,
}
}
}
impl Handler {
fn new(
embedder_sender: Sender<WebDriverCommandMsg>,
event_loop_waker: Box<dyn EventLoopWaker>,
default_preferences: Preferences,
) -> Handler {
let (load_status_sender, receiver) = generic_channel::channel().unwrap();
let load_status_receiver = receiver.route_preserving_errors();
Handler {
load_status_sender,
load_status_receiver,
session: None,
embedder_sender,
event_loop_waker,
default_preferences,
pending_input_event_receivers: Default::default(),
pending_actions: Default::default(),
}
}
fn browsing_context_id(&self) -> WebDriverResult<BrowsingContextId> {
self.session()?
.current_browsing_context_id()
.ok_or_else(|| {
WebDriverError::new(ErrorStatus::UnknownError, "No browsing context available")
})
}
fn webview_id(&self) -> WebDriverResult<WebViewId> {
self.session()?
.current_webview_id()
.ok_or_else(|| WebDriverError::new(ErrorStatus::UnknownError, "No webview available"))
}
fn send_input_event_to_embedder(&self, input_event: InputEvent) {
let _ = self.send_message_to_embedder(WebDriverCommandMsg::InputEvent(
self.verified_webview_id(),
input_event,
None,
));
}
fn send_blocking_input_event_to_embedder(&mut self, input_event: InputEvent) {
let (result_sender, result_receiver) = unbounded();
if self
.send_message_to_embedder(WebDriverCommandMsg::InputEvent(
self.verified_webview_id(),
input_event,
Some(result_sender),
))
.is_ok()
{
self.pending_input_event_receivers.push(result_receiver);
}
}
fn send_message_to_embedder(&self, msg: WebDriverCommandMsg) -> WebDriverResult<()> {
self.embedder_sender.send(msg).map_err(|_| {
WebDriverError::new(
ErrorStatus::UnknownError,
"Failed to send message to embedder",
)
})?;
self.event_loop_waker.wake();
Ok(())
}
fn add_load_status_sender(&self) -> WebDriverResult<()> {
self.send_message_to_embedder(WebDriverCommandMsg::ScriptCommand(
self.browsing_context_id()?,
WebDriverScriptCommand::AddLoadStatusSender(
self.webview_id()?,
self.load_status_sender.clone(),
),
))
}
fn clear_load_status_sender(&self) -> WebDriverResult<()> {
self.send_message_to_embedder(WebDriverCommandMsg::ScriptCommand(
self.browsing_context_id()?,
WebDriverScriptCommand::RemoveLoadStatusSender(self.webview_id()?),
))
}
fn verified_webview_id(&self) -> WebViewId {
self.session().unwrap().current_webview_id().unwrap()
}
fn focused_webview_id(&self) -> WebDriverResult<Option<WebViewId>> {
let (sender, receiver) = generic_channel::oneshot().unwrap();
self.send_message_to_embedder(WebDriverCommandMsg::GetFocusedWebView(sender))?;
wait_for_oneshot_response(receiver)
}
fn session(&self) -> WebDriverResult<&WebDriverSession> {
match self.session {
Some(ref x) => Ok(x),
None => Err(WebDriverError::new(
ErrorStatus::InvalidSessionId,
"Session not created",
)),
}
}
fn session_mut(&mut self) -> WebDriverResult<&mut WebDriverSession> {
match self.session {
Some(ref mut x) => Ok(x),
None => Err(WebDriverError::new(
ErrorStatus::InvalidSessionId,
"Session not created",
)),
}
}
fn handle_new_session(
&mut self,
parameters: &NewSessionParameters,
) -> WebDriverResult<WebDriverResponse> {
if let Ok(value) = env::var("DELAY_AFTER_ACCEPT") {
let seconds = value.parse::<u64>().unwrap_or_default();
println!("Waiting for {} seconds...", seconds);
println!("lldb -p {}", process::id());
thread::sleep(Duration::from_secs(seconds));
}
if self.session.is_some() {
return Err(WebDriverError::new(
ErrorStatus::SessionNotCreated,
"Session already created",
));
}
let mut servo_capabilities = ServoCapabilities::new();
let processed_capabilities = parameters.match_browser(&mut servo_capabilities)?;
let mut capabilities = match processed_capabilities {
Some(capabilities) => capabilities,
None => {
return Err(WebDriverError::new(
ErrorStatus::SessionNotCreated,
"Session not created due to invalid capabilities",
));
},
};
let session_id = self.create_session(&mut capabilities, &servo_capabilities)?;
let response = NewSessionResponse::new(session_id.to_string(), Value::Object(capabilities));
match self.focused_webview_id()? {
Some(webview_id) => {
self.session_mut()?.set_webview_id(webview_id);
self.wait_until_browsing_context_is_open(BrowsingContextId::from(webview_id))?;
self.session_mut()?
.set_browsing_context_id(BrowsingContextId::from(webview_id));
},
None => {
let (sender, receiver) = generic_channel::oneshot().unwrap();
self.send_message_to_embedder(WebDriverCommandMsg::NewWindow(
NewWindowTypeHint::Auto,
sender,
Some(self.load_status_sender.clone()),
))?;
let webview_id = receiver
.recv()
.expect("IPC failure when creating new webview for new session");
self.focus_webview(webview_id)?;
self.session_mut()?.set_webview_id(webview_id);
self.wait_until_browsing_context_is_open(BrowsingContextId::from(webview_id))?;
self.session_mut()?
.set_browsing_context_id(BrowsingContextId::from(webview_id));
let _ = self.wait_document_ready(Some(DEFAULT_PAGE_LOAD_TIMEOUT));
},
};
Ok(WebDriverResponse::NewSession(response))
}
fn handle_delete_session(&mut self) -> WebDriverResult<WebDriverResponse> {
self.session = None;
Ok(WebDriverResponse::DeleteSession)
}
fn handle_status(&self) -> WebDriverResult<WebDriverResponse> {
Ok(WebDriverResponse::Generic(ValueResponse(
if self.session.is_none() {
json!({ "ready": true, "message": "Ready for a new session" })
} else {
json!({ "ready": false, "message": "Not ready for a new session" })
},
)))
}
fn browsing_context_script_command(
&self,
cmd_msg: WebDriverScriptCommand,
verify: VerifyBrowsingContextIsOpen,
) -> WebDriverResult<()> {
let browsing_context_id = self.browsing_context_id()?;
if let VerifyBrowsingContextIsOpen::Yes = verify {
self.verify_browsing_context_is_open(browsing_context_id)?;
}
self.send_message_to_embedder(WebDriverCommandMsg::ScriptCommand(
browsing_context_id,
cmd_msg,
))?;
Ok(())
}
fn top_level_script_command(
&self,
cmd_msg: WebDriverScriptCommand,
verify: VerifyBrowsingContextIsOpen,
) -> WebDriverResult<()> {
let webview_id = self.webview_id()?;
if let VerifyBrowsingContextIsOpen::Yes = verify {
self.verify_top_level_browsing_context_is_open(webview_id)?;
}
let browsing_context_id = BrowsingContextId::from(webview_id);
self.send_message_to_embedder(WebDriverCommandMsg::ScriptCommand(
browsing_context_id,
cmd_msg,
))?;
Ok(())
}
fn handle_get(&mut self, parameters: &GetParameters) -> WebDriverResult<WebDriverResponse> {
let webview_id = self.webview_id()?;
self.verify_top_level_browsing_context_is_open(webview_id)?;
let url = ServoUrl::parse(¶meters.url)
.map(|url| url.into_url())
.map_err(|_| WebDriverError::new(ErrorStatus::InvalidArgument, "Invalid URL"))?;
self.handle_any_user_prompts(webview_id)?;
let cmd_msg =
WebDriverCommandMsg::LoadUrl(webview_id, url, self.load_status_sender.clone());
self.send_message_to_embedder(cmd_msg)?;
self.wait_for_navigation_complete()?;
self.session_mut()?
.set_browsing_context_id(BrowsingContextId::from(webview_id));
Ok(WebDriverResponse::Void)
}
fn wait_document_ready(&self, timeout: Option<u64>) -> WebDriverResult<WebDriverResponse> {
let timeout_channel = match timeout {
Some(timeout) => after(Duration::from_millis(timeout)),
None => crossbeam_channel::never(),
};
select! {
recv(self.load_status_receiver) -> res => {
match res {
Ok(Ok(WebDriverLoadStatus::Blocked)) => {
Ok(WebDriverResponse::Void)
},
Ok(Ok(WebDriverLoadStatus::Complete)) |
Ok(Ok(WebDriverLoadStatus::NavigationStop)) =>
Ok(WebDriverResponse::Void)
,
_ => Err(WebDriverError::new(
ErrorStatus::UnknownError,
"Unexpected load status received while waiting for document ready state",
)),
}
},
recv(timeout_channel) -> _ => Err(
WebDriverError::new(ErrorStatus::Timeout, "Load timed out")
),
}
}
fn wait_for_navigation_complete(&self) -> WebDriverResult<WebDriverResponse> {
debug!("waiting for load");
let session = self.session()?;
if session.page_loading_strategy() == PageLoadStrategy::None {
return Ok(WebDriverResponse::Void);
}
if self
.verify_browsing_context_is_open(self.browsing_context_id()?)
.is_err()
{
return Ok(WebDriverResponse::Void);
}
let timeout = session.session_timeouts().page_load;
let result = self.wait_document_ready(timeout);
debug!("finished waiting for load with {:?}", result);
result
}
fn wait_for_navigation(&self) -> WebDriverResult<WebDriverResponse> {
let navigation_status = match self.load_status_receiver.try_recv() {
Ok(Ok(status)) => status,
Err(crossbeam_channel::TryRecvError::Empty) => {
return Ok(WebDriverResponse::Void);
},
Err(crossbeam_channel::TryRecvError::Disconnected) => {
return Err(WebDriverError::new(
ErrorStatus::UnknownError,
"Load status channel disconnected",
));
},
Ok(Err(ipc_error)) => {
return Err(WebDriverError::new(
ErrorStatus::UnknownError,
format!("Load status channel ipc error: {ipc_error}"),
));
},
};
match navigation_status {
WebDriverLoadStatus::NavigationStart => self.wait_for_navigation_complete(),
WebDriverLoadStatus::Timeout => Err(WebDriverError::new(
ErrorStatus::Timeout,
"Navigation timed out",
)),
WebDriverLoadStatus::Blocked => Ok(WebDriverResponse::Void),
WebDriverLoadStatus::NavigationStop | WebDriverLoadStatus::Complete => {
unreachable!("Unexpected load status received")
},
}
}
fn handle_current_url(&self) -> WebDriverResult<WebDriverResponse> {
let webview_id = self.webview_id()?;
self.verify_top_level_browsing_context_is_open(webview_id)?;
self.handle_any_user_prompts(webview_id)?;
let (sender, receiver) = generic_channel::channel().unwrap();
self.top_level_script_command(
WebDriverScriptCommand::GetUrl(sender),
VerifyBrowsingContextIsOpen::No,
)?;
let url = wait_for_ipc_response(receiver)?;
Ok(WebDriverResponse::Generic(ValueResponse(
serde_json::to_value(url)?,
)))
}
fn handle_window_rect(
&self,
verify: VerifyBrowsingContextIsOpen,
) -> WebDriverResult<WebDriverResponse> {
let (sender, receiver) = generic_channel::oneshot().unwrap();
let webview_id = self.webview_id()?;
if let VerifyBrowsingContextIsOpen::Yes = verify {
self.verify_top_level_browsing_context_is_open(webview_id)?;
}
self.handle_any_user_prompts(webview_id)?;
self.send_message_to_embedder(WebDriverCommandMsg::GetWindowRect(webview_id, sender))?;
let window_rect = wait_for_oneshot_response(receiver)?;
let window_size_response = WindowRectResponse {
x: window_rect.min.x,
y: window_rect.min.y,
width: window_rect.width(),
height: window_rect.height(),
};
Ok(WebDriverResponse::WindowRect(window_size_response))
}
fn handle_set_window_rect(
&self,
params: &WindowRectParameters,
) -> WebDriverResult<WebDriverResponse> {
let webview_id = self.webview_id()?;
self.verify_top_level_browsing_context_is_open(webview_id)?;
self.handle_any_user_prompts(webview_id)?;
let current = LazyCell::new(|| {
let WebDriverResponse::WindowRect(current) = self
.handle_window_rect(VerifyBrowsingContextIsOpen::No)
.unwrap()
else {
unreachable!("handle_window_size() must return WindowRect");
};
current
});
let (x, y, width, height) = (
params.x.unwrap_or_else(|| current.x),
params.y.unwrap_or_else(|| current.y),
params.width.unwrap_or_else(|| current.width),
params.height.unwrap_or_else(|| current.height),
);
let (sender, receiver) = generic_channel::oneshot().unwrap();
self.send_message_to_embedder(WebDriverCommandMsg::SetWindowRect(
webview_id,
DeviceIndependentIntRect::from_origin_and_size(
Point2D::new(x, y),
Size2D::new(width, height),
),
sender,
))?;
let window_rect = wait_for_oneshot_response(receiver)?;
debug!("Result window_rect: {window_rect:?}");
let window_size_response = WindowRectResponse {
x: window_rect.min.x,
y: window_rect.min.y,
width: window_rect.width(),
height: window_rect.height(),
};
Ok(WebDriverResponse::WindowRect(window_size_response))
}
fn handle_maximize_window(&mut self) -> WebDriverResult<WebDriverResponse> {
let webview_id = self.webview_id()?;
self.verify_top_level_browsing_context_is_open(webview_id)?;
self.handle_any_user_prompts(self.webview_id()?)?;
let (sender, receiver) = generic_channel::oneshot().unwrap();
self.send_message_to_embedder(WebDriverCommandMsg::MaximizeWebView(webview_id, sender))?;
let window_rect = wait_for_oneshot_response(receiver)?;
debug!("Result window_rect: {window_rect:?}");
let window_size_response = WindowRectResponse {
x: window_rect.min.x,
y: window_rect.min.y,
width: window_rect.width(),
height: window_rect.height(),
};
Ok(WebDriverResponse::WindowRect(window_size_response))
}
fn handle_is_enabled(&self, element: &WebElement) -> WebDriverResult<WebDriverResponse> {
let browsing_context = self.browsing_context_id()?;
self.verify_browsing_context_is_open(browsing_context)?;
let webview_id = self.webview_id()?;
self.handle_any_user_prompts(webview_id)?;
let (sender, receiver) = generic_channel::channel().unwrap();
self.browsing_context_script_command(
WebDriverScriptCommand::IsEnabled(element.to_string(), sender),
VerifyBrowsingContextIsOpen::No,
)?;
Ok(WebDriverResponse::Generic(ValueResponse(
serde_json::to_value(wait_for_ipc_response_flatten(receiver)?)?,
)))
}
fn handle_is_selected(&self, element: &WebElement) -> WebDriverResult<WebDriverResponse> {
let browsing_context = self.browsing_context_id()?;
self.verify_browsing_context_is_open(browsing_context)?;
let webview_id = self.webview_id()?;
self.handle_any_user_prompts(webview_id)?;
let (sender, receiver) = generic_channel::channel().unwrap();
self.browsing_context_script_command(
WebDriverScriptCommand::IsSelected(element.to_string(), sender),
VerifyBrowsingContextIsOpen::No,
)?;
Ok(WebDriverResponse::Generic(ValueResponse(
serde_json::to_value(wait_for_ipc_response_flatten(receiver)?)?,
)))
}
fn handle_go_back(&self) -> WebDriverResult<WebDriverResponse> {
let webview_id = self.webview_id()?;
self.verify_top_level_browsing_context_is_open(webview_id)?;
self.handle_any_user_prompts(webview_id)?;
self.send_message_to_embedder(WebDriverCommandMsg::GoBack(
webview_id,
self.load_status_sender.clone(),
))?;
self.wait_for_navigation_complete()
}
fn handle_go_forward(&self) -> WebDriverResult<WebDriverResponse> {
let webview_id = self.webview_id()?;
self.verify_top_level_browsing_context_is_open(webview_id)?;
self.handle_any_user_prompts(webview_id)?;
self.send_message_to_embedder(WebDriverCommandMsg::GoForward(
webview_id,
self.load_status_sender.clone(),
))?;
self.wait_for_navigation_complete()
}
fn handle_refresh(&mut self) -> WebDriverResult<WebDriverResponse> {
let webview_id = self.webview_id()?;
self.verify_top_level_browsing_context_is_open(webview_id)?;
self.handle_any_user_prompts(webview_id)?;
let cmd_msg = WebDriverCommandMsg::Refresh(webview_id, self.load_status_sender.clone());
self.send_message_to_embedder(cmd_msg)?;
self.wait_for_navigation_complete()?;
self.session_mut()?
.set_browsing_context_id(BrowsingContextId::from(webview_id));
Ok(WebDriverResponse::Void)
}
fn handle_title(&self) -> WebDriverResult<WebDriverResponse> {
let webview_id = self.webview_id()?;
self.verify_top_level_browsing_context_is_open(webview_id)?;
self.handle_any_user_prompts(webview_id)?;
let (sender, receiver) = generic_channel::channel().unwrap();
self.top_level_script_command(
WebDriverScriptCommand::GetTitle(sender),
VerifyBrowsingContextIsOpen::No,
)?;
let title = wait_for_ipc_response(receiver)?;
Ok(WebDriverResponse::Generic(ValueResponse(
serde_json::to_value(title)?,
)))
}
fn handle_window_handle(&mut self) -> WebDriverResult<WebDriverResponse> {
let webview_id = self.webview_id()?;
self.verify_top_level_browsing_context_is_open(webview_id)?;
let handle = self
.get_window_handle(webview_id)
.expect("Failed to get window handle of an existing webview");
Ok(WebDriverResponse::Generic(ValueResponse(
serde_json::to_value(handle)?,
)))
}
fn handle_window_handles(&mut self) -> WebDriverResult<WebDriverResponse> {
let mut handles = self.get_window_handles();
handles.sort_unstable();
Ok(WebDriverResponse::Generic(ValueResponse(
serde_json::to_value(handles)?,
)))
}
fn get_window_handle(&mut self, webview_id: WebViewId) -> Option<String> {
self.get_window_handles()
.iter()
.find(|id| id == &&webview_id.to_string())
.cloned()
}
fn get_window_handles(&self) -> Vec<String> {
self.get_all_webview_ids()
.into_iter()
.map(|id| id.to_string())
.collect()
}
fn get_all_webview_ids(&self) -> Vec<WebViewId> {
let (sender, receiver) = generic_channel::oneshot().unwrap();
self.send_message_to_embedder(WebDriverCommandMsg::GetAllWebViews(sender))
.unwrap();
wait_for_oneshot_response(receiver).unwrap_or_default()
}
fn handle_close_window(&mut self) -> WebDriverResult<WebDriverResponse> {
let webview_id = self.webview_id()?;
self.verify_top_level_browsing_context_is_open(webview_id)?;
self.handle_any_user_prompts(webview_id)?;
let (sender, receiver) = generic_channel::oneshot().unwrap();
let cmd_msg = WebDriverCommandMsg::CloseWebView(webview_id, sender);
self.send_message_to_embedder(cmd_msg)?;
wait_for_oneshot_response(receiver)?;
let window_handles = self.get_window_handles();
if window_handles.is_empty() {
self.session = None;
}
Ok(WebDriverResponse::CloseWindow(CloseWindowResponse(
window_handles,
)))
}
fn handle_new_window(
&mut self,
parameters: &NewWindowParameters,
) -> WebDriverResult<WebDriverResponse> {
let (sender, receiver) = generic_channel::oneshot().unwrap();
let webview_id = self.webview_id()?;
self.verify_top_level_browsing_context_is_open(webview_id)?;
self.handle_any_user_prompts(webview_id)?;
let type_hint = match parameters.type_hint.as_deref() {
Some("tab") => NewWindowTypeHint::Tab,
Some("window") => NewWindowTypeHint::Window,
_ => NewWindowTypeHint::Auto,
};
self.send_message_to_embedder(WebDriverCommandMsg::NewWindow(
type_hint,
sender,
Some(self.load_status_sender.clone()),
))?;
if let Ok(webview_id) = receiver.recv() {
let _ = self.wait_for_navigation_complete();
let handle = self
.get_window_handle(webview_id)
.expect("Failed to get window handle of an existing webview");
Ok(WebDriverResponse::NewWindow(NewWindowResponse {
handle,
typ: "tab".to_string(),
}))
} else {
Err(WebDriverError::new(
ErrorStatus::UnknownError,
"No webview ID received",
))
}
}
fn handle_switch_to_frame(
&mut self,
parameters: &SwitchToFrameParameters,
) -> WebDriverResult<WebDriverResponse> {
use webdriver::common::FrameId;
let frame_id = match parameters.id {
FrameId::Top => {
let webview_id = self.webview_id()?;
self.verify_top_level_browsing_context_is_open(webview_id)?;
self.handle_any_user_prompts(webview_id)?;
let browsing_context_id = BrowsingContextId::from(webview_id);
self.session_mut()?
.set_browsing_context_id(browsing_context_id);
self.focus_browsing_context(browsing_context_id)?;
return Ok(WebDriverResponse::Void);
},
FrameId::Short(ref x) => {
WebDriverFrameId::Short(*x)
},
FrameId::Element(ref x) => WebDriverFrameId::Element(x.to_string()),
};
self.switch_to_frame(frame_id)
}
fn handle_switch_to_parent_frame(&mut self) -> WebDriverResult<WebDriverResponse> {
let webview_id = self.webview_id()?;
let browsing_context = self.browsing_context_id()?;
if browsing_context == webview_id {
self.verify_browsing_context_is_open(browsing_context)?;
return Ok(WebDriverResponse::Void);
}
let (sender, receiver) = generic_channel::channel().unwrap();
let cmd = WebDriverScriptCommand::GetParentFrameId(sender);
self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::Yes)?;
self.handle_any_user_prompts(webview_id)?;
let browsing_context_id = wait_for_ipc_response_flatten(receiver)?;
self.session_mut()?
.set_browsing_context_id(browsing_context_id);
self.focus_browsing_context(browsing_context_id)?;
Ok(WebDriverResponse::Void)
}
fn handle_switch_to_window(
&mut self,
parameters: &SwitchToWindowParameters,
) -> WebDriverResult<WebDriverResponse> {
let Some(webview_id) = self
.get_all_webview_ids()
.into_iter()
.find(|id| id.to_string() == parameters.handle)
else {
return Err(WebDriverError::new(
ErrorStatus::NoSuchWindow,
"No such window while switching to window",
));
};
let session = self.session_mut()?;
session.set_webview_id(webview_id);
session.set_browsing_context_id(BrowsingContextId::from(webview_id));
self.focus_webview(webview_id)?;
Ok(WebDriverResponse::Void)
}
fn switch_to_frame(
&mut self,
frame_id: WebDriverFrameId,
) -> WebDriverResult<WebDriverResponse> {
let (sender, receiver) = generic_channel::channel().unwrap();
let cmd = WebDriverScriptCommand::GetBrowsingContextId(frame_id, sender);
self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::Yes)?;
self.handle_any_user_prompts(self.webview_id()?)?;
let browsing_context_id = wait_for_ipc_response_flatten(receiver)?;
self.session_mut()?
.set_browsing_context_id(browsing_context_id);
self.focus_browsing_context(browsing_context_id)?;
Ok(WebDriverResponse::Void)
}
fn handle_find_element(
&self,
parameters: &LocatorParameters,
) -> WebDriverResult<WebDriverResponse> {
let res = self.handle_find_elements(parameters)?;
unwrap_first_element_response(res)
}
fn implicit_wait<T>(
&self,
callback: impl Fn() -> Result<(bool, T), (bool, WebDriverError)>,
) -> Result<T, WebDriverError> {
let now = Instant::now();
let (implicit_wait, sleep_interval) = {
let timeouts = self.session()?.session_timeouts();
(
timeouts
.implicit_wait
.map_or(Duration::MAX, Duration::from_millis),
Duration::from_millis(timeouts.sleep_interval),
)
};
loop {
match callback() {
Ok((can_early_return, value)) => {
if can_early_return || now.elapsed() >= implicit_wait {
return Ok(value);
}
},
Err((can_early_return, error)) => {
if can_early_return || now.elapsed() >= implicit_wait {
return Err(error);
}
},
}
sleep(sleep_interval);
}
}
fn handle_find_elements(
&self,
parameters: &LocatorParameters,
) -> WebDriverResult<WebDriverResponse> {
if parameters.value.is_empty() {
return Err(WebDriverError::new(ErrorStatus::InvalidArgument, ""));
}
self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
self.handle_any_user_prompts(self.webview_id()?)?;
self.implicit_wait(|| {
let (sender, receiver) = generic_channel::channel().unwrap();
let cmd = match parameters.using {
LocatorStrategy::CSSSelector => WebDriverScriptCommand::FindElementsCSSSelector(
parameters.value.clone(),
sender,
),
LocatorStrategy::LinkText | LocatorStrategy::PartialLinkText => {
WebDriverScriptCommand::FindElementsLinkText(
parameters.value.clone(),
parameters.using == LocatorStrategy::PartialLinkText,
sender,
)
},
LocatorStrategy::TagName => {
WebDriverScriptCommand::FindElementsTagName(parameters.value.clone(), sender)
},
LocatorStrategy::XPath => WebDriverScriptCommand::FindElementsXpathSelector(
parameters.value.clone(),
sender,
),
};
self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)
.map_err(|error| (ImplicitWait::Return.into(), error))?;
wait_for_ipc_response_flatten(receiver)
.map(|value| (!value.is_empty(), value))
.map_err(|error| (ImplicitWait::Return.into(), error))
})
.and_then(|response| {
let resp_value: Vec<WebElement> = response.into_iter().map(WebElement).collect();
Ok(WebDriverResponse::Generic(ValueResponse(
serde_json::to_value(resp_value)?,
)))
})
}
fn handle_find_element_from_element(
&self,
element: &WebElement,
parameters: &LocatorParameters,
) -> WebDriverResult<WebDriverResponse> {
let res = self.handle_find_elements_from_element(element, parameters)?;
unwrap_first_element_response(res)
}
fn handle_find_elements_from_element(
&self,
element: &WebElement,
parameters: &LocatorParameters,
) -> WebDriverResult<WebDriverResponse> {
if parameters.value.is_empty() {
return Err(WebDriverError::new(ErrorStatus::InvalidArgument, ""));
}
self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
self.handle_any_user_prompts(self.webview_id()?)?;
self.implicit_wait(|| {
let (sender, receiver) = generic_channel::channel().unwrap();
let cmd = match parameters.using {
LocatorStrategy::CSSSelector => {
WebDriverScriptCommand::FindElementElementsCSSSelector(
parameters.value.clone(),
element.to_string(),
sender,
)
},
LocatorStrategy::LinkText | LocatorStrategy::PartialLinkText => {
WebDriverScriptCommand::FindElementElementsLinkText(
parameters.value.clone(),
element.to_string(),
parameters.using == LocatorStrategy::PartialLinkText,
sender,
)
},
LocatorStrategy::TagName => WebDriverScriptCommand::FindElementElementsTagName(
parameters.value.clone(),
element.to_string(),
sender,
),
LocatorStrategy::XPath => WebDriverScriptCommand::FindElementElementsXPathSelector(
parameters.value.clone(),
element.to_string(),
sender,
),
};
self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)
.map_err(|error| (ImplicitWait::Return.into(), error))?;
wait_for_ipc_response_flatten(receiver)
.map(|value| (!value.is_empty(), value))
.map_err(|error| (ImplicitWait::Return.into(), error))
})
.and_then(|response| {
let resp_value: Vec<Value> = response
.into_iter()
.map(|x| serde_json::to_value(WebElement(x)).unwrap())
.collect();
Ok(WebDriverResponse::Generic(ValueResponse(
serde_json::to_value(resp_value)?,
)))
})
}
fn handle_find_elements_from_shadow_root(
&self,
shadow_root: &ShadowRoot,
parameters: &LocatorParameters,
) -> WebDriverResult<WebDriverResponse> {
if parameters.value.is_empty() {
return Err(WebDriverError::new(ErrorStatus::InvalidArgument, ""));
}
self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
self.handle_any_user_prompts(self.webview_id()?)?;
self.implicit_wait(|| {
let (sender, receiver) = generic_channel::channel().unwrap();
let cmd = match parameters.using {
LocatorStrategy::CSSSelector => {
WebDriverScriptCommand::FindShadowElementsCSSSelector(
parameters.value.clone(),
shadow_root.to_string(),
sender,
)
},
LocatorStrategy::LinkText | LocatorStrategy::PartialLinkText => {
WebDriverScriptCommand::FindShadowElementsLinkText(
parameters.value.clone(),
shadow_root.to_string(),
parameters.using == LocatorStrategy::PartialLinkText,
sender,
)
},
LocatorStrategy::TagName => WebDriverScriptCommand::FindShadowElementsTagName(
parameters.value.clone(),
shadow_root.to_string(),
sender,
),
LocatorStrategy::XPath => WebDriverScriptCommand::FindShadowElementsXPathSelector(
parameters.value.clone(),
shadow_root.to_string(),
sender,
),
};
self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)
.map_err(|error| (ImplicitWait::Return.into(), error))?;
wait_for_ipc_response_flatten(receiver)
.map(|value| (!value.is_empty(), value))
.map_err(|error| (ImplicitWait::Return.into(), error))
})
.and_then(|response| {
let resp_value: Vec<Value> = response
.into_iter()
.map(|x| serde_json::to_value(WebElement(x)).unwrap())
.collect();
Ok(WebDriverResponse::Generic(ValueResponse(
serde_json::to_value(resp_value)?,
)))
})
}
fn handle_find_element_from_shadow_root(
&self,
shadow_root: &ShadowRoot,
parameters: &LocatorParameters,
) -> WebDriverResult<WebDriverResponse> {
let res = self.handle_find_elements_from_shadow_root(shadow_root, parameters)?;
unwrap_first_element_response(res)
}
fn handle_get_shadow_root(&self, element: WebElement) -> WebDriverResult<WebDriverResponse> {
self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
self.handle_any_user_prompts(self.webview_id()?)?;
let (sender, receiver) = generic_channel::channel().unwrap();
let cmd = WebDriverScriptCommand::GetElementShadowRoot(element.to_string(), sender);
self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
let Some(value) = wait_for_ipc_response_flatten(receiver)? else {
return Err(WebDriverError::new(ErrorStatus::NoSuchShadowRoot, ""));
};
Ok(WebDriverResponse::Generic(ValueResponse(
serde_json::to_value(ShadowRoot(value))?,
)))
}
fn handle_element_rect(&self, element: &WebElement) -> WebDriverResult<WebDriverResponse> {
self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
self.handle_any_user_prompts(self.webview_id()?)?;
let (sender, receiver) = generic_channel::channel().unwrap();
let cmd = WebDriverScriptCommand::GetElementRect(element.to_string(), sender);
self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
let rect = wait_for_ipc_response_flatten(receiver)?;
let response = ElementRectResponse {
x: rect.origin.x,
y: rect.origin.y,
width: rect.size.width,
height: rect.size.height,
};
Ok(WebDriverResponse::ElementRect(response))
}
fn handle_element_text(&self, element: &WebElement) -> WebDriverResult<WebDriverResponse> {
self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
self.handle_any_user_prompts(self.webview_id()?)?;
let (sender, receiver) = generic_channel::channel().unwrap();
let cmd = WebDriverScriptCommand::GetElementText(element.to_string(), sender);
self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
Ok(WebDriverResponse::Generic(ValueResponse(
serde_json::to_value(wait_for_ipc_response_flatten(receiver)?)?,
)))
}
fn handle_active_element(&self) -> WebDriverResult<WebDriverResponse> {
self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
self.handle_any_user_prompts(self.webview_id()?)?;
let (sender, receiver) = generic_channel::channel().unwrap();
let cmd = WebDriverScriptCommand::GetActiveElement(sender);
self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
let value =
wait_for_ipc_response(receiver)?.map(|x| serde_json::to_value(WebElement(x)).unwrap());
if value.is_some() {
Ok(WebDriverResponse::Generic(ValueResponse(
serde_json::to_value(value)?,
)))
} else {
Err(WebDriverError::new(
ErrorStatus::NoSuchElement,
"No active element found",
))
}
}
fn handle_computed_role(&self, element: &WebElement) -> WebDriverResult<WebDriverResponse> {
self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
self.handle_any_user_prompts(self.webview_id()?)?;
let (sender, receiver) = generic_channel::channel().unwrap();
let cmd = WebDriverScriptCommand::GetComputedRole(element.to_string(), sender);
self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
Ok(WebDriverResponse::Generic(ValueResponse(
serde_json::to_value(wait_for_ipc_response_flatten(receiver)?)?,
)))
}
fn handle_element_tag_name(&self, element: &WebElement) -> WebDriverResult<WebDriverResponse> {
self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
self.handle_any_user_prompts(self.webview_id()?)?;
let (sender, receiver) = generic_channel::channel().unwrap();
let cmd = WebDriverScriptCommand::GetElementTagName(element.to_string(), sender);
self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
Ok(WebDriverResponse::Generic(ValueResponse(
serde_json::to_value(wait_for_ipc_response_flatten(receiver)?)?,
)))
}
fn handle_element_attribute(
&self,
element: &WebElement,
name: &str,
) -> WebDriverResult<WebDriverResponse> {
self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
self.handle_any_user_prompts(self.webview_id()?)?;
let (sender, receiver) = generic_channel::channel().unwrap();
let cmd = WebDriverScriptCommand::GetElementAttribute(
element.to_string(),
name.to_owned(),
sender,
);
self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
Ok(WebDriverResponse::Generic(ValueResponse(
serde_json::to_value(wait_for_ipc_response_flatten(receiver)?)?,
)))
}
fn handle_element_property(
&self,
element: &WebElement,
name: &str,
) -> WebDriverResult<WebDriverResponse> {
self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
self.handle_any_user_prompts(self.webview_id()?)?;
let (sender, receiver) = generic_channel::channel().unwrap();
let cmd = WebDriverScriptCommand::GetElementProperty(
element.to_string(),
name.to_owned(),
sender,
);
self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
Ok(WebDriverResponse::Generic(ValueResponse(
serde_json::to_value(SendableJSValue(wait_for_ipc_response_flatten(receiver)?))?,
)))
}
fn handle_element_css(
&self,
element: &WebElement,
name: &str,
) -> WebDriverResult<WebDriverResponse> {
self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
self.handle_any_user_prompts(self.webview_id()?)?;
let (sender, receiver) = generic_channel::channel().unwrap();
let cmd =
WebDriverScriptCommand::GetElementCSS(element.to_string(), name.to_owned(), sender);
self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
Ok(WebDriverResponse::Generic(ValueResponse(
serde_json::to_value(wait_for_ipc_response_flatten(receiver)?)?,
)))
}
fn handle_get_cookies(&self) -> WebDriverResult<WebDriverResponse> {
self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
self.handle_any_user_prompts(self.webview_id()?)?;
let (sender, receiver) = generic_channel::channel().unwrap();
let cmd = WebDriverScriptCommand::GetCookies(sender);
self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
let cookies = wait_for_ipc_response_flatten(receiver)?;
let response = cookies
.into_iter()
.map(|cookie| cookie_msg_to_cookie(cookie.into_inner()))
.collect::<Vec<Cookie>>();
Ok(WebDriverResponse::Cookies(CookiesResponse(response)))
}
fn handle_get_cookie(&self, name: String) -> WebDriverResult<WebDriverResponse> {
self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
self.handle_any_user_prompts(self.webview_id()?)?;
let (sender, receiver) = generic_channel::channel().unwrap();
let cmd = WebDriverScriptCommand::GetCookie(name, sender);
self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
let cookies = wait_for_ipc_response_flatten(receiver)?;
let Some(response) = cookies
.into_iter()
.map(|cookie| cookie_msg_to_cookie(cookie.into_inner()))
.next()
else {
return Err(WebDriverError::new(ErrorStatus::NoSuchCookie, ""));
};
Ok(WebDriverResponse::Cookie(CookieResponse(response)))
}
fn handle_add_cookie(
&self,
params: &AddCookieParameters,
) -> WebDriverResult<WebDriverResponse> {
self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
self.handle_any_user_prompts(self.webview_id()?)?;
let (sender, receiver) = generic_channel::channel().unwrap();
if let Some(ref expiry) = params.expiry &&
expiry.0 > MAXIMUM_SAFE_INTEGER
{
return Err(WebDriverError::new(
ErrorStatus::InvalidArgument,
"expiry time greater than maximum safe integer",
));
}
let mut cookie_builder =
CookieBuilder::new(params.name.to_owned(), params.value.to_owned())
.secure(params.secure)
.http_only(params.httpOnly);
if let Some(ref domain) = params.domain {
cookie_builder = cookie_builder.domain(domain.clone());
}
if let Some(ref path) = params.path {
cookie_builder = cookie_builder.path(path.clone());
}
if let Some(ref expiry) = params.expiry {
let datetime = OffsetDateTime::from_unix_timestamp(expiry.0 as i64).map_err(|_| {
WebDriverError::new(ErrorStatus::InvalidArgument, "invalid expiry time")
})?;
cookie_builder = cookie_builder.expires(datetime);
}
if let Some(ref same_site) = params.sameSite {
cookie_builder = match same_site.as_str() {
"None" => Ok(cookie_builder.same_site(SameSite::None)),
"Lax" => Ok(cookie_builder.same_site(SameSite::Lax)),
"Strict" => Ok(cookie_builder.same_site(SameSite::Strict)),
_ => Err(WebDriverError::new(
ErrorStatus::InvalidArgument,
"invalid argument",
)),
}?;
}
let cmd = WebDriverScriptCommand::AddCookie(cookie_builder.build(), sender);
self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
wait_for_ipc_response_flatten(receiver)?;
Ok(WebDriverResponse::Void)
}
fn handle_delete_cookie(&self, name: String) -> WebDriverResult<WebDriverResponse> {
self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
self.handle_any_user_prompts(self.webview_id()?)?;
let (sender, receiver) = generic_channel::channel().unwrap();
let cmd = WebDriverScriptCommand::DeleteCookie(name, sender);
self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
wait_for_ipc_response_flatten(receiver)?;
Ok(WebDriverResponse::Void)
}
fn handle_delete_cookies(&self) -> WebDriverResult<WebDriverResponse> {
self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
self.handle_any_user_prompts(self.webview_id()?)?;
let (sender, receiver) = generic_channel::channel().unwrap();
let cmd = WebDriverScriptCommand::DeleteCookies(sender);
self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
wait_for_ipc_response_flatten(receiver)?;
Ok(WebDriverResponse::Void)
}
fn handle_get_timeouts(&mut self) -> WebDriverResult<WebDriverResponse> {
let timeouts = self.session()?.session_timeouts();
let timeouts = TimeoutsResponse {
script: timeouts.script,
page_load: timeouts.page_load.unwrap_or(DEFAULT_PAGE_LOAD_TIMEOUT),
implicit: timeouts.implicit_wait.unwrap_or(DEFAULT_IMPLICIT_WAIT),
};
Ok(WebDriverResponse::Timeouts(timeouts))
}
fn handle_set_timeouts(
&mut self,
parameters: &TimeoutsParameters,
) -> WebDriverResult<WebDriverResponse> {
let session = self.session_mut()?;
if let Some(timeout) = parameters.script {
session.session_timeouts_mut().script = timeout;
}
if let Some(timeout) = parameters.page_load {
session.session_timeouts_mut().page_load = Some(timeout);
}
if let Some(timeout) = parameters.implicit {
session.session_timeouts_mut().implicit_wait = Some(timeout);
}
Ok(WebDriverResponse::Void)
}
fn handle_get_page_source(&self) -> WebDriverResult<WebDriverResponse> {
self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
self.handle_any_user_prompts(self.webview_id()?)?;
let (sender, receiver) = generic_channel::channel().unwrap();
let cmd = WebDriverScriptCommand::GetPageSource(sender);
self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
Ok(WebDriverResponse::Generic(ValueResponse(
serde_json::to_value(wait_for_ipc_response_flatten(receiver)?)?,
)))
}
fn handle_perform_actions(
&mut self,
parameters: ActionsParameters,
) -> WebDriverResult<WebDriverResponse> {
let browsing_context = self.browsing_context_id()?;
self.verify_browsing_context_is_open(browsing_context)?;
self.handle_any_user_prompts(self.webview_id()?)?;
let actions_by_tick = self.extract_an_action_sequence(parameters.actions);
match self.dispatch_actions(actions_by_tick, browsing_context) {
Ok(_) => Ok(WebDriverResponse::Void),
Err(error) => Err(WebDriverError::new(error, "")),
}
}
fn handle_release_actions(&mut self) -> WebDriverResult<WebDriverResponse> {
let browsing_context_id = self.browsing_context_id()?;
self.verify_browsing_context_is_open(browsing_context_id)?;
self.handle_any_user_prompts(self.webview_id()?)?;
let undo_actions = self
.session_mut()?
.input_cancel_list
.drain(..)
.rev()
.map(|(id, action_item)| Vec::from([(id, action_item)]))
.collect();
if let Err(err) = self.dispatch_actions(undo_actions, browsing_context_id) {
return Err(WebDriverError::new(err, "Failed to dispatch undo actions"));
}
self.session_mut()?.input_state_table.clear();
Ok(WebDriverResponse::Void)
}
fn handle_execute_script(
&self,
parameters: JavascriptCommandParameters,
) -> WebDriverResult<WebDriverResponse> {
let (func_body, args_string) = self.extract_script_arguments(parameters)?;
let script = format!(
r#"(async function(__wd_eid) {{
try {{
let result = (async function() {{
{func_body}
}})({});
let value = await result;
if (window.__wd_eid === __wd_eid) {{
window.webdriverCallback(value);
}}
}} catch (err) {{
if (window.__wd_eid === __wd_eid) {{
window.webdriverException(err);
}}
}}
}})(window.__wd_eid = (window.__wd_eid || 0) + 1);"#,
args_string.join(", ")
);
debug!("{}", script);
self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
self.handle_any_user_prompts(self.webview_id()?)?;
let (sender, receiver) = generic_channel::channel().unwrap();
let cmd = WebDriverScriptCommand::ExecuteScriptWithCallback(script, sender);
self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
let timeout_duration = self
.session()?
.session_timeouts()
.script
.map_or(Duration::MAX, Duration::from_millis);
let result = wait_for_script_ipc_response_with_timeout(receiver, timeout_duration)?;
self.javascript_evaluation_result_to_webdriver_response(result)
}
fn handle_execute_async_script(
&self,
parameters: JavascriptCommandParameters,
) -> WebDriverResult<WebDriverResponse> {
let (function_body, mut args_string) = self.extract_script_arguments(parameters)?;
args_string.push("resolve".to_string());
let joined_args = args_string.join(", ");
let script = format!(
r#"(async function(__wd_eid) {{
try {{
let result = new Promise(function(resolve, reject) {{
(async function() {{
{function_body}
}})({joined_args})
.catch(reject)
}});
let value = await result;
if (window.__wd_eid === __wd_eid) {{
window.webdriverCallback(value);
}}
}} catch (err) {{
if (window.__wd_eid === __wd_eid) {{
window.webdriverException(err);
}}
}}
}})(window.__wd_eid = (window.__wd_eid || 0) + 1);"#,
);
debug!("{}", script);
self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
self.handle_any_user_prompts(self.webview_id()?)?;
let (sender, receiver) = generic_channel::channel().unwrap();
self.browsing_context_script_command(
WebDriverScriptCommand::ExecuteScriptWithCallback(script, sender),
VerifyBrowsingContextIsOpen::No,
)?;
let timeout_duration = self
.session()?
.session_timeouts()
.script
.map_or(Duration::MAX, Duration::from_millis);
let result = wait_for_script_ipc_response_with_timeout(receiver, timeout_duration)?;
self.javascript_evaluation_result_to_webdriver_response(result)
}
fn javascript_evaluation_result_to_webdriver_response(
&self,
result: WebDriverJSResult,
) -> WebDriverResult<WebDriverResponse> {
match result {
Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse(
serde_json::to_value(SendableJSValue(value))?,
))),
Err(error) => {
let message = format!("{error:?}");
let status = match error {
JavaScriptEvaluationError::DocumentNotFound => ErrorStatus::NoSuchWindow,
JavaScriptEvaluationError::CompilationFailure => ErrorStatus::JavascriptError,
JavaScriptEvaluationError::EvaluationFailure(Some(error_info)) => {
return Err(WebDriverError::new_with_data(
ErrorStatus::JavascriptError,
error_info.message,
None,
error_info.stack,
));
},
JavaScriptEvaluationError::EvaluationFailure(None) => {
ErrorStatus::JavascriptError
},
JavaScriptEvaluationError::InternalError => ErrorStatus::JavascriptError,
JavaScriptEvaluationError::SerializationError(serialization_error) => {
match serialization_error {
JavaScriptEvaluationResultSerializationError::DetachedShadowRoot => {
ErrorStatus::DetachedShadowRoot
},
JavaScriptEvaluationResultSerializationError::OtherJavaScriptError => {
ErrorStatus::JavascriptError
},
JavaScriptEvaluationResultSerializationError::StaleElementReference => {
ErrorStatus::StaleElementReference
},
JavaScriptEvaluationResultSerializationError::UnknownType => {
ErrorStatus::UnsupportedOperation
},
}
},
JavaScriptEvaluationError::WebViewNotReady => ErrorStatus::NoSuchWindow,
};
Err(WebDriverError::new(status, message))
},
}
}
fn handle_element_send_keys(
&mut self,
element: &WebElement,
keys: &SendKeysParameters,
) -> WebDriverResult<WebDriverResponse> {
self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
self.handle_any_user_prompts(self.webview_id()?)?;
let (sender, receiver) = generic_channel::channel().unwrap();
let cmd = WebDriverScriptCommand::WillSendKeys(
element.to_string(),
keys.text.to_string(),
self.session()?.strict_file_interactability(),
sender,
);
self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
if !wait_for_ipc_response_flatten(receiver)? {
return Ok(WebDriverResponse::Void);
}
let id = Uuid::new_v4().to_string();
self.session_mut()?
.input_state_table
.insert(id.clone(), InputSourceState::Key(KeyInputState::new()));
let input_events = send_keys(&keys.text);
for event in input_events {
match event {
DispatchStringEvent::Keyboard(event) => {
let raw_string = convert_keyboard_event_to_string(&event);
let key_action = match event.state {
KeyState::Down => KeyAction::Down(KeyDownAction { value: raw_string }),
KeyState::Up => KeyAction::Up(KeyUpAction { value: raw_string }),
};
let action_sequence = ActionSequence {
id: id.clone(),
actions: ActionsType::Key {
actions: vec![KeyActionItem::Key(key_action)],
},
};
let actions_by_tick = self.extract_an_action_sequence(vec![action_sequence]);
if let Err(e) =
self.dispatch_actions(actions_by_tick, self.browsing_context_id()?)
{
error!("handle_element_send_keys: dispatch_actions failed: {:?}", e);
}
},
DispatchStringEvent::Composition(event) => {
self.send_blocking_input_event_to_embedder(InputEvent::Ime(
ImeEvent::Composition(event),
));
},
}
}
if self
.session()?
.input_cancel_list
.iter()
.all(|(cancel_item_id, _)| &id != cancel_item_id)
{
self.session_mut()?.input_state_table.remove(&id);
}
Ok(WebDriverResponse::Void)
}
fn handle_element_clear(&self, element: &WebElement) -> WebDriverResult<WebDriverResponse> {
self.verify_browsing_context_is_open(self.browsing_context_id()?)?;
self.handle_any_user_prompts(self.webview_id()?)?;
let (sender, receiver) = generic_channel::channel().unwrap();
let cmd = WebDriverScriptCommand::ElementClear(element.to_string(), sender);
self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
wait_for_ipc_response_flatten(receiver)?;
Ok(WebDriverResponse::Void)
}
fn handle_element_click(&mut self, element: &WebElement) -> WebDriverResult<WebDriverResponse> {
let browsing_context_id = self.browsing_context_id()?;
self.verify_browsing_context_is_open(browsing_context_id)?;
self.handle_any_user_prompts(self.webview_id()?)?;
let (sender, receiver) = generic_channel::channel().unwrap();
let cmd = WebDriverScriptCommand::ElementClick(element.to_string(), sender);
self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::No)?;
match wait_for_ipc_response_flatten(receiver)? {
Some(element_id) => {
self.add_load_status_sender()?;
self.perform_element_click(element_id)?;
let res = self.wait_for_navigation()?;
self.clear_load_status_sender()?;
Ok(res)
},
None => Ok(WebDriverResponse::Void),
}
}
fn perform_element_click(&mut self, element: String) -> WebDriverResult<WebDriverResponse> {
let id = Uuid::new_v4().to_string();
let pointer_ids = self.session()?.pointer_ids();
#[cfg(not(any(target_env = "ohos", target_os = "android")))]
let pointer_type = PointerType::Mouse;
#[cfg(any(target_env = "ohos", target_os = "android"))]
let pointer_type = PointerType::Touch;
self.session_mut()?.input_state_table.insert(
id.clone(),
InputSourceState::Pointer(PointerInputState::new(pointer_type, pointer_ids, 0.0, 0.0)),
);
let pointer_move_action = PointerMoveAction {
duration: None,
origin: PointerOrigin::Element(WebElement(element)),
x: 0.0,
y: 0.0,
..Default::default()
};
let pointer_down_action = PointerDownAction {
button: ELEMENT_CLICK_BUTTON,
..Default::default()
};
let pointer_up_action = PointerUpAction {
button: ELEMENT_CLICK_BUTTON,
..Default::default()
};
let action_sequence = ActionSequence {
id: id.clone(),
actions: ActionsType::Pointer {
parameters: PointerActionParameters {
pointer_type: PointerType::Mouse,
},
actions: vec![
PointerActionItem::Pointer(PointerAction::Move(pointer_move_action)),
PointerActionItem::Pointer(PointerAction::Down(pointer_down_action)),
PointerActionItem::Pointer(PointerAction::Up(pointer_up_action)),
],
},
};
let actions_by_tick = self.extract_an_action_sequence(vec![action_sequence]);
if let Err(e) = self.dispatch_actions(actions_by_tick, self.browsing_context_id()?) {
error!("handle_element_click: dispatch_actions failed: {:?}", e);
}
self.session_mut()?.input_state_table.remove(&id);
Ok(WebDriverResponse::Void)
}
fn take_screenshot(&self, rect: Option<Rect<f32, CSSPixel>>) -> WebDriverResult<String> {
let _ = self.handle_execute_async_script(JavascriptCommandParameters {
script: "requestAnimationFrame(() => arguments[0]());".to_string(),
args: None,
});
if rect.as_ref().is_some_and(Rect::is_empty) {
return Err(WebDriverError::new(
ErrorStatus::UnknownError,
"The requested `rect` has zero width and/or height",
));
}
let webview_id = self.webview_id()?;
let (sender, receiver) = crossbeam_channel::unbounded();
self.send_message_to_embedder(WebDriverCommandMsg::TakeScreenshot(
webview_id, rect, sender,
))?;
let result = match receiver.recv_timeout(SCREENSHOT_TIMEOUT) {
Ok(result) => Ok(result),
Err(RecvTimeoutError::Timeout) => Err(WebDriverError::new(
ErrorStatus::Timeout,
"Timed out waiting to take screenshot. Test likely didn't finish.",
)),
Err(RecvTimeoutError::Disconnected) => Err(WebDriverError::new(
ErrorStatus::UnknownError,
"Could not take screenshot because channel disconnected.",
)),
}?;
let image = result.map_err(|error| {
WebDriverError::new(
ErrorStatus::UnknownError,
format!("Failed to take screenshot: {error:?}"),
)
})?;
let mut png_data = Cursor::new(Vec::new());
DynamicImage::ImageRgba8(image)
.write_to(&mut png_data, ImageFormat::Png)
.unwrap();
Ok(base64::engine::general_purpose::STANDARD.encode(png_data.get_ref()))
}
fn handle_take_screenshot(&self) -> WebDriverResult<WebDriverResponse> {
let webview_id = self.webview_id()?;
self.verify_top_level_browsing_context_is_open(webview_id)?;
self.handle_any_user_prompts(webview_id)?;
let encoded = self.take_screenshot(None)?;
Ok(WebDriverResponse::Generic(ValueResponse(
serde_json::to_value(encoded)?,
)))
}
fn handle_take_element_screenshot(
&self,
element: &WebElement,
) -> WebDriverResult<WebDriverResponse> {
let webview_id = self.webview_id()?;
self.verify_top_level_browsing_context_is_open(webview_id)?;
self.handle_any_user_prompts(webview_id)?;
let (sender, receiver) = generic_channel::channel().unwrap();
let cmd =
WebDriverScriptCommand::ScrollAndGetBoundingClientRect(element.to_string(), sender);
self.browsing_context_script_command(cmd, VerifyBrowsingContextIsOpen::Yes)?;
let rect = wait_for_ipc_response_flatten(receiver)?;
let encoded = self.take_screenshot(Some(Rect::from_untyped(&rect)))?;
Ok(WebDriverResponse::Generic(ValueResponse(
serde_json::to_value(encoded)?,
)))
}
fn handle_custom_handlers_set_mode(
&self,
parameters: &CustomHandlersSetModeParameters,
) -> WebDriverResult<WebDriverResponse> {
let mode = match parameters.mode.as_str() {
"autoAccept" => CustomHandlersAutomationMode::AutoAccept,
"autoReject" => CustomHandlersAutomationMode::AutoReject,
"none" => CustomHandlersAutomationMode::None,
_ => {
return Err(WebDriverError::new(
ErrorStatus::InvalidArgument,
"invalid argument",
));
},
};
self.top_level_script_command(
WebDriverScriptCommand::SetProtocolHandlerAutomationMode(mode),
VerifyBrowsingContextIsOpen::Yes,
)?;
Ok(WebDriverResponse::Void)
}
fn handle_get_prefs(
&self,
parameters: &GetPrefsParameters,
) -> WebDriverResult<WebDriverResponse> {
let prefs = parameters
.prefs
.iter()
.map(|item| {
(
item.clone(),
serde_json::to_value(prefs::get().get_value(item)).unwrap(),
)
})
.collect::<BTreeMap<_, _>>();
Ok(WebDriverResponse::Generic(ValueResponse(
serde_json::to_value(prefs)?,
)))
}
fn handle_set_prefs(
&self,
parameters: &SetPrefsParameters,
) -> WebDriverResult<WebDriverResponse> {
let mut current_preferences = prefs::get().clone();
for (key, value) in parameters.prefs.iter() {
current_preferences.set_value(key, value.0.clone());
}
prefs::set(current_preferences);
Ok(WebDriverResponse::Void)
}
fn handle_reset_prefs(
&self,
parameters: &GetPrefsParameters,
) -> WebDriverResult<WebDriverResponse> {
let (new_preferences, map) = if parameters.prefs.is_empty() {
(self.default_preferences.clone(), BTreeMap::new())
} else {
let mut new_preferences = prefs::get().clone();
for key in parameters.prefs.iter() {
new_preferences.set_value(key, self.default_preferences.get_value(key))
}
let map = parameters
.prefs
.iter()
.map(|item| (item.clone(), new_preferences.get_value(item)))
.collect::<BTreeMap<_, _>>();
(new_preferences, map)
};
prefs::set(new_preferences);
Ok(WebDriverResponse::Generic(ValueResponse(
serde_json::to_value(map)?,
)))
}
fn handle_shutdown(&self) -> WebDriverResult<WebDriverResponse> {
self.send_message_to_embedder(WebDriverCommandMsg::Shutdown)?;
Ok(WebDriverResponse::Void)
}
fn handle_reset_all_cookies(&self) -> WebDriverResult<WebDriverResponse> {
let (sender, receiver) = unbounded();
self.send_message_to_embedder(WebDriverCommandMsg::ResetAllCookies(sender))?;
if receiver.recv().is_err() {
log::warn!("Communication failure while clearing cookies; status unknown");
}
Ok(WebDriverResponse::Void)
}
fn verify_top_level_browsing_context_is_open(
&self,
webview_id: WebViewId,
) -> Result<(), WebDriverError> {
let (sender, receiver) = generic_channel::oneshot().unwrap();
self.send_message_to_embedder(WebDriverCommandMsg::IsWebViewOpen(webview_id, sender))?;
if wait_for_oneshot_response(receiver)? {
Ok(())
} else {
Err(WebDriverError::new(ErrorStatus::NoSuchWindow, ""))
}
}
fn verify_browsing_context_is_open(
&self,
browsing_context_id: BrowsingContextId,
) -> Result<(), WebDriverError> {
let (sender, receiver) = generic_channel::oneshot().unwrap();
self.send_message_to_embedder(WebDriverCommandMsg::IsBrowsingContextOpen(
browsing_context_id,
sender,
))?;
if !receiver.recv().unwrap_or(false) {
Err(WebDriverError::new(
ErrorStatus::NoSuchWindow,
"No such window",
))
} else {
Ok(())
}
}
fn wait_until_browsing_context_is_open(
&self,
browsing_context_id: BrowsingContextId,
) -> WebDriverResult<()> {
const OPEN_BROWSING_CONTEXT_TIMEOUT: Duration =
Duration::from_millis(DEFAULT_PAGE_LOAD_TIMEOUT);
let now = Instant::now();
let timeouts = self.session()?.session_timeouts();
let sleep_interval = Duration::from_millis(timeouts.sleep_interval);
while now.elapsed() < OPEN_BROWSING_CONTEXT_TIMEOUT {
if self
.verify_browsing_context_is_open(browsing_context_id)
.is_ok()
{
return Ok(());
}
sleep(sleep_interval);
}
Err(WebDriverError::new(
ErrorStatus::Timeout,
format!(
"Timed out waiting for the top-level browsing context {browsing_context_id} to be ready"
),
))
}
fn focus_webview(&self, webview_id: WebViewId) -> WebDriverResult<()> {
self.send_message_to_embedder(WebDriverCommandMsg::FocusWebView(webview_id))
}
fn focus_browsing_context(&self, browsing_cotext_id: BrowsingContextId) -> WebDriverResult<()> {
self.send_message_to_embedder(WebDriverCommandMsg::FocusBrowsingContext(
browsing_cotext_id,
))
}
}
impl WebDriverHandler<ServoExtensionRoute> for Handler {
fn handle_command(
&mut self,
_session: &Option<Session>,
msg: WebDriverMessage<ServoExtensionRoute>,
) -> WebDriverResult<WebDriverResponse> {
info!("{:?}", msg.command);
while self.load_status_receiver.try_recv().is_ok() {}
match msg.command {
WebDriverCommand::NewSession(_) |
WebDriverCommand::Status |
WebDriverCommand::DeleteSession |
WebDriverCommand::Extension(ServoExtensionCommand::Shutdown) |
WebDriverCommand::Extension(ServoExtensionCommand::ResetAllCookies) => {},
_ => {
self.session()?;
},
}
match msg.command {
WebDriverCommand::NewSession(ref parameters) => self.handle_new_session(parameters),
WebDriverCommand::DeleteSession => self.handle_delete_session(),
WebDriverCommand::Status => self.handle_status(),
WebDriverCommand::AddCookie(ref parameters) => self.handle_add_cookie(parameters),
WebDriverCommand::Get(ref parameters) => self.handle_get(parameters),
WebDriverCommand::GetCurrentUrl => self.handle_current_url(),
WebDriverCommand::GetWindowRect => {
self.handle_window_rect(VerifyBrowsingContextIsOpen::Yes)
},
WebDriverCommand::SetWindowRect(ref size) => self.handle_set_window_rect(size),
WebDriverCommand::IsEnabled(ref element) => self.handle_is_enabled(element),
WebDriverCommand::IsSelected(ref element) => self.handle_is_selected(element),
WebDriverCommand::GoBack => self.handle_go_back(),
WebDriverCommand::GoForward => self.handle_go_forward(),
WebDriverCommand::Refresh => self.handle_refresh(),
WebDriverCommand::GetTitle => self.handle_title(),
WebDriverCommand::GetWindowHandle => self.handle_window_handle(),
WebDriverCommand::GetWindowHandles => self.handle_window_handles(),
WebDriverCommand::NewWindow(ref parameters) => self.handle_new_window(parameters),
WebDriverCommand::CloseWindow => self.handle_close_window(),
WebDriverCommand::MaximizeWindow => self.handle_maximize_window(),
WebDriverCommand::SwitchToFrame(ref parameters) => {
self.handle_switch_to_frame(parameters)
},
WebDriverCommand::SwitchToParentFrame => self.handle_switch_to_parent_frame(),
WebDriverCommand::SwitchToWindow(ref parameters) => {
self.handle_switch_to_window(parameters)
},
WebDriverCommand::FindElement(ref parameters) => self.handle_find_element(parameters),
WebDriverCommand::FindElements(ref parameters) => self.handle_find_elements(parameters),
WebDriverCommand::FindElementElement(ref element, ref parameters) => {
self.handle_find_element_from_element(element, parameters)
},
WebDriverCommand::FindElementElements(ref element, ref parameters) => {
self.handle_find_elements_from_element(element, parameters)
},
WebDriverCommand::FindShadowRootElements(ref shadow_root, ref parameters) => {
self.handle_find_elements_from_shadow_root(shadow_root, parameters)
},
WebDriverCommand::FindShadowRootElement(ref shadow_root, ref parameters) => {
self.handle_find_element_from_shadow_root(shadow_root, parameters)
},
WebDriverCommand::GetShadowRoot(element) => self.handle_get_shadow_root(element),
WebDriverCommand::GetNamedCookie(name) => self.handle_get_cookie(name),
WebDriverCommand::GetCookies => self.handle_get_cookies(),
WebDriverCommand::GetActiveElement => self.handle_active_element(),
WebDriverCommand::GetComputedRole(ref element) => self.handle_computed_role(element),
WebDriverCommand::GetElementRect(ref element) => self.handle_element_rect(element),
WebDriverCommand::GetElementText(ref element) => self.handle_element_text(element),
WebDriverCommand::GetElementTagName(ref element) => {
self.handle_element_tag_name(element)
},
WebDriverCommand::GetElementAttribute(ref element, ref name) => {
self.handle_element_attribute(element, name)
},
WebDriverCommand::GetElementProperty(ref element, ref name) => {
self.handle_element_property(element, name)
},
WebDriverCommand::GetCSSValue(ref element, ref name) => {
self.handle_element_css(element, name)
},
WebDriverCommand::GetPageSource => self.handle_get_page_source(),
WebDriverCommand::PerformActions(actions_parameters) => {
self.handle_perform_actions(actions_parameters)
},
WebDriverCommand::ReleaseActions => self.handle_release_actions(),
WebDriverCommand::ExecuteScript(x) => self.handle_execute_script(x),
WebDriverCommand::ExecuteAsyncScript(x) => self.handle_execute_async_script(x),
WebDriverCommand::ElementSendKeys(ref element, ref keys) => {
self.handle_element_send_keys(element, keys)
},
WebDriverCommand::ElementClear(ref element) => self.handle_element_clear(element),
WebDriverCommand::ElementClick(ref element) => self.handle_element_click(element),
WebDriverCommand::DismissAlert => self.handle_dismiss_alert(),
WebDriverCommand::AcceptAlert => self.handle_accept_alert(),
WebDriverCommand::GetAlertText => self.handle_get_alert_text(),
WebDriverCommand::SendAlertText(text) => self.handle_send_alert_text(text.text),
WebDriverCommand::DeleteCookies => self.handle_delete_cookies(),
WebDriverCommand::DeleteCookie(name) => self.handle_delete_cookie(name),
WebDriverCommand::GetTimeouts => self.handle_get_timeouts(),
WebDriverCommand::SetTimeouts(ref x) => self.handle_set_timeouts(x),
WebDriverCommand::TakeScreenshot => self.handle_take_screenshot(),
WebDriverCommand::TakeElementScreenshot(ref x) => {
self.handle_take_element_screenshot(x)
},
WebDriverCommand::Extension(extension) => match extension {
ServoExtensionCommand::GetPrefs(ref x) => self.handle_get_prefs(x),
ServoExtensionCommand::SetPrefs(ref x) => self.handle_set_prefs(x),
ServoExtensionCommand::ResetPrefs(ref x) => self.handle_reset_prefs(x),
ServoExtensionCommand::CustomHandlersSetMode(ref x) => {
self.handle_custom_handlers_set_mode(x)
},
ServoExtensionCommand::Shutdown => self.handle_shutdown(),
ServoExtensionCommand::ResetAllCookies => self.handle_reset_all_cookies(),
},
_ => Err(WebDriverError::new(
ErrorStatus::UnsupportedOperation,
format!("Command not implemented: {:?}", msg.command),
)),
}
}
fn teardown_session(&mut self, _session: SessionTeardownKind) {
self.session = None;
}
}
fn wait_for_oneshot_response<T>(
receiver: generic_channel::GenericOneshotReceiver<T>,
) -> Result<T, WebDriverError>
where
T: for<'de> Deserialize<'de> + Serialize,
{
receiver
.recv()
.map_err(|_| WebDriverError::new(ErrorStatus::NoSuchWindow, ""))
}
fn wait_for_ipc_response<T>(receiver: GenericReceiver<T>) -> Result<T, WebDriverError>
where
T: for<'de> Deserialize<'de> + Serialize,
{
receiver
.recv()
.map_err(|_| WebDriverError::new(ErrorStatus::NoSuchWindow, ""))
}
fn wait_for_ipc_response_flatten<T>(
receiver: GenericReceiver<Result<T, ErrorStatus>>,
) -> Result<T, WebDriverError>
where
T: for<'de> Deserialize<'de> + Serialize,
{
match receiver.recv() {
Ok(Ok(value)) => Ok(value),
Ok(Err(error_status)) => Err(WebDriverError::new(error_status, "")),
Err(_) => Err(WebDriverError::new(ErrorStatus::NoSuchWindow, "")),
}
}
fn wait_for_script_ipc_response_with_timeout<T>(
receiver: GenericReceiver<T>,
timeout: Duration,
) -> Result<T, WebDriverError>
where
T: for<'de> Deserialize<'de> + Serialize,
{
receiver
.try_recv_timeout(timeout)
.map_err(|error| match error {
generic_channel::TryReceiveError::ReceiveError(_) => {
WebDriverError::new(ErrorStatus::NoSuchWindow, "")
},
generic_channel::TryReceiveError::Empty => {
WebDriverError::new(ErrorStatus::ScriptTimeout, "")
},
})
}
fn unwrap_first_element_response(res: WebDriverResponse) -> WebDriverResult<WebDriverResponse> {
if let WebDriverResponse::Generic(ValueResponse(values)) = res {
let arr = values.as_array().unwrap();
if let Some(first) = arr.first() {
Ok(WebDriverResponse::Generic(ValueResponse(first.clone())))
} else {
Err(WebDriverError::new(ErrorStatus::NoSuchElement, ""))
}
} else {
unreachable!()
}
}
fn convert_keyboard_event_to_string(event: &KeyboardEvent) -> String {
let key = &event.key;
let named_key = match key {
Key::Character(s) => return s.to_string(),
Key::Named(named_key) => named_key,
};
match event.location {
Location::Left | Location::Standard => match named_key {
NamedKey::Unidentified => '\u{E000}'.to_string(),
NamedKey::Cancel => '\u{E001}'.to_string(),
NamedKey::Help => '\u{E002}'.to_string(),
NamedKey::Backspace => '\u{E003}'.to_string(),
NamedKey::Tab => '\u{E004}'.to_string(),
NamedKey::Clear => '\u{E005}'.to_string(),
NamedKey::Enter => match event.code {
Code::NumpadEnter => '\u{E007}'.to_string(),
_ => '\u{E006}'.to_string(),
},
NamedKey::Shift => '\u{E008}'.to_string(),
NamedKey::Control => '\u{E009}'.to_string(),
NamedKey::Alt => '\u{E00A}'.to_string(),
NamedKey::Pause => '\u{E00B}'.to_string(),
NamedKey::Escape => '\u{E00C}'.to_string(),
NamedKey::PageUp => '\u{E00E}'.to_string(),
NamedKey::PageDown => '\u{E00F}'.to_string(),
NamedKey::End => '\u{E010}'.to_string(),
NamedKey::Home => '\u{E011}'.to_string(),
NamedKey::ArrowLeft => '\u{E012}'.to_string(),
NamedKey::ArrowUp => '\u{E013}'.to_string(),
NamedKey::ArrowRight => '\u{E014}'.to_string(),
NamedKey::ArrowDown => '\u{E015}'.to_string(),
NamedKey::Insert => '\u{E016}'.to_string(),
NamedKey::Delete => '\u{E017}'.to_string(),
NamedKey::F1 => '\u{E031}'.to_string(),
NamedKey::F2 => '\u{E032}'.to_string(),
NamedKey::F3 => '\u{E033}'.to_string(),
NamedKey::F4 => '\u{E034}'.to_string(),
NamedKey::F5 => '\u{E035}'.to_string(),
NamedKey::F6 => '\u{E036}'.to_string(),
NamedKey::F7 => '\u{E037}'.to_string(),
NamedKey::F8 => '\u{E038}'.to_string(),
NamedKey::F9 => '\u{E039}'.to_string(),
NamedKey::F10 => '\u{E03A}'.to_string(),
NamedKey::F11 => '\u{E03B}'.to_string(),
NamedKey::F12 => '\u{E03C}'.to_string(),
NamedKey::Meta => '\u{E03D}'.to_string(),
NamedKey::ZenkakuHankaku => '\u{E040}'.to_string(),
_ => {
error!("Unexpected NamedKey on send_keys");
'\u{E000}'.to_string()
},
},
Location::Right | Location::Numpad => match named_key {
NamedKey::Shift => '\u{E050}'.to_string(),
NamedKey::Control => '\u{E051}'.to_string(),
NamedKey::Alt => '\u{E052}'.to_string(),
NamedKey::Meta => '\u{E053}'.to_string(),
NamedKey::PageUp => '\u{E054}'.to_string(),
NamedKey::PageDown => '\u{E055}'.to_string(),
NamedKey::End => '\u{E056}'.to_string(),
NamedKey::Home => '\u{E057}'.to_string(),
NamedKey::ArrowLeft => '\u{E058}'.to_string(),
NamedKey::ArrowUp => '\u{E059}'.to_string(),
NamedKey::ArrowRight => '\u{E05A}'.to_string(),
NamedKey::ArrowDown => '\u{E05B}'.to_string(),
NamedKey::Insert => '\u{E05C}'.to_string(),
NamedKey::Delete => '\u{E05D}'.to_string(),
_ => {
error!("Unexpected NamedKey on send_keys");
'\u{E000}'.to_string()
},
},
}
}