/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
usecrate::browser::{Browser, BrowserStatus, LocalBrowser, RemoteBrowser}; usecrate::build; usecrate::capabilities::{FirefoxCapabilities, FirefoxOptions, ProfileType}; usecrate::command::{
AddonInstallParameters, GeckoContext, GeckoExtensionCommand, GeckoExtensionRoute,
}; usecrate::logging; use marionette_rs::common::{
Cookie as MarionetteCookie, Date as MarionetteDate, Frame as MarionetteFrame,
Timeouts as MarionetteTimeouts, WebElement as MarionetteWebElement, Window,
}; use marionette_rs::marionette::AppStatus; use marionette_rs::message::{Command, Message, MessageId, Request}; use marionette_rs::webdriver::{
AddonInstallParameters as MarionetteAddonInstallParameters,
AuthenticatorIdParameters as MarionetteAuthenticatorIdParameters,
AuthenticatorParameters as MarionetteAuthenticatorParameters,
AuthenticatorTransport as MarionetteAuthenticatorTransport,
Command as MarionetteWebDriverCommand,
CredentialIdParameters as MarionetteCredentialIdParameters,
CredentialParameters as MarionetteCredentialParameters,
GeckoContext as MarionetteGeckoContext,
GlobalPrivacyControlParameters as MarionetteGlobalPrivacyControlParameters,
Credentials as MarionetteCredentials,
Keys as MarionetteKeys, Locator as MarionetteLocator, NewWindow as MarionetteNewWindow,
PrintMargins as MarionettePrintMargins, PrintOrientation as MarionettePrintOrientation,
PrintPage as MarionettePrintPage, PrintPageRange as MarionettePrintPageRange,
PrintParameters as MarionettePrintParameters, ScreenshotOptions, Script as MarionetteScript,
Selector as MarionetteSelector, SetPermissionDescriptor as MarionetteSetPermissionDescriptor,
SetPermissionParameters as MarionetteSetPermissionParameters,
SetPermissionState as MarionetteSetPermissionState,
UserVerificationParameters as MarionetteUserVerificationParameters,
AuthenticatorProtocol as MarionetteAuthenticatorProtocol,
WindowRect as MarionetteWindowRect,
}; use mozdevice::AndroidStorageInput; use serde::de::{self, Deserialize, Deserializer}; use serde_json::{Map, Value}; use std::borrow::Cow; use std::collections::BTreeMap; use std::io::Error as IoError; use std::io::Result as IoResult; use std::io::prelude::*; use std::net::{Shutdown, TcpListener, TcpStream}; use std::path::PathBuf; use std::sync::Mutex; use std::thread; use std::time; use url::{Host, Url}; use webdriver::capabilities::BrowserCapabilities; use webdriver::command::WebDriverCommand::{
AcceptAlert, AddCookie, CloseWindow, DeleteCookie, DeleteCookies, DeleteSession, DismissAlert,
ElementClear, ElementClick, ElementSendKeys, ExecuteAsyncScript, ExecuteScript, Extension,
FindElement, FindElementElement, FindElementElements, FindElements, FindShadowRootElement,
FindShadowRootElements, FullscreenWindow, GPCGetGlobalPrivacyControl,
GPCSetGlobalPrivacyControl, Get, GetActiveElement, GetAlertText, GetCSSValue, GetComputedLabel,
GetComputedRole, GetCookies, GetCurrentUrl, GetElementAttribute, GetElementProperty,
GetElementRect, GetElementTagName, GetElementText, GetNamedCookie, GetPageSource,
GetShadowRoot, GetTimeouts, GetTitle, GetWindowHandle, GetWindowHandles, GetWindowRect, GoBack,
GoForward, IsDisplayed, IsEnabled, IsSelected, MaximizeWindow, MinimizeWindow, NewSession,
NewWindow, PerformActions, Print, Refresh, ReleaseActions, SendAlertText, SetPermission,
SetTimeouts, SetWindowRect, Status, SwitchToFrame, SwitchToParentFrame, SwitchToWindow,
TakeElementScreenshot, TakeScreenshot, WebAuthnAddCredential, WebAuthnAddVirtualAuthenticator,
WebAuthnGetCredentials, WebAuthnRemoveAllCredentials, WebAuthnRemoveCredential,
WebAuthnRemoveVirtualAuthenticator, WebAuthnSetUserVerified,
}; use webdriver::command::{
ActionsParameters, AddCookieParameters, AuthenticatorParameters, AuthenticatorTransport,
GetNamedCookieParameters, GlobalPrivacyControlParameters, JavascriptCommandParameters,
LocatorParameters, NewSessionParameters, NewWindowParameters, PrintMargins, PrintOrientation,
PrintPage, PrintPageRange, PrintParameters, SendKeysParameters, SetPermissionDescriptor,
SetPermissionParameters, SetPermissionState, SwitchToFrameParameters, SwitchToWindowParameters,
TimeoutsParameters, AuthenticatorProtocol, WindowRectParameters,
}; use webdriver::command::{WebDriverCommand, WebDriverMessage}; use webdriver::common::{
Cookie, Credentials, Date, ELEMENT_KEY, FRAME_KEY,
FrameId, LocatorStrategy, SHADOW_KEY, ShadowRoot, WebElement, WINDOW_KEY,
}; use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult}; use webdriver::response::{
CloseWindowResponse, CookieResponse, CookiesResponse, ElementRectResponse, NewSessionResponse,
NewWindowResponse, TimeoutsResponse, ValueResponse, WebDriverResponse, WindowRectResponse,
}; use webdriver::server::{Session, WebDriverHandler}; use webdriver::{capabilities::CapabilitiesMatching, server::SessionTeardownKind};
let marionette_host = self.settings.host.to_owned(); let marionette_port = matchself.settings.port {
Some(port) => port,
None => { // If we're launching Firefox Desktop version 95 or later, and there's no port // specified, we can pass 0 as the port and later read it back from // the profile. let can_use_profile: bool = options.android.is_none()
&& options.profile != ProfileType::Named
&& !self.settings.connect_existing
&& fx_capabilities
.browser_version(&capabilities)
.map(|opt_v| {
opt_v
.map(|v| {
fx_capabilities
.compare_browser_version(&v, ">=95")
.unwrap_or(false)
})
.unwrap_or(false)
})
.unwrap_or(false); if can_use_profile { 0
} else {
get_free_port(&marionette_host)?
}
}
};
let websocket_port = if options.use_websocket {
Some(self.settings.websocket_port)
} else {
None
};
let browser = if options.android.is_some() { // TODO: support connecting to running Apps. There's no real obstruction here, // just some details about port forwarding to work through. We can't follow // `chromedriver` here since it uses an abstract socket rather than a TCP socket: // see bug 1240830 for thoughts on doing that for Marionette. ifself.settings.connect_existing { return Err(WebDriverError::new(
ErrorStatus::SessionNotCreated, "Cannot connect to an existing Android App yet",
));
}
Browser::Remote(RemoteBrowser::new(
options,
marionette_port,
websocket_port, self.settings.system_access, self.settings.profile_root.as_deref(),
)?)
} elseif !self.settings.connect_existing {
Browser::Local(LocalBrowser::new(
options,
marionette_port, self.settings.jsdebugger, self.settings.system_access, self.settings.profile_root.as_deref(),
)?)
} else {
Browser::Existing(marionette_port)
}; let session = MarionetteSession::new(session_id, capabilities);
MarionetteConnection::new(marionette_host, browser, session)
}
fn close_connection(&mutself, wait_for_shutdown: bool) { iflet Ok(connection) = self.connection.get_mut()
&& let Some(conn) = connection.take()
&& let Err(e) = conn.close(wait_for_shutdown)
{
error!("Failed to close browser connection: {}", e)
}
}
}
impl WebDriverHandler<GeckoExtensionRoute> for MarionetteHandler { fn handle_command(
&mutself,
_: &Option<Session>,
msg: WebDriverMessage<GeckoExtensionRoute>,
) -> WebDriverResult<WebDriverResponse> { // First handle the status message which doesn't actually require a marionette // connection or message iflet Status = msg.command { let (ready, message) = self
.connection
.get_mut()
.map(|ref connection| {
connection
.as_ref()
.map(|_| (false, "Session already started"))
.unwrap_or((true, ""))
})
.unwrap_or((false, "geckodriver internal error")); letmut value = Map::new();
value.insert("ready".to_string(), Value::Bool(ready));
value.insert("message".to_string(), Value::String(message.into())); return Ok(WebDriverResponse::Generic(ValueResponse(Value::Object(
value,
))));
}
matchself.connection.lock() {
Ok(mut connection) => { if connection.is_none() { iflet NewSession(ref capabilities) = msg.command { let conn = self.create_connection(msg.session_id.clone(), capabilities)?;
*connection = Some(conn);
} else { return Err(WebDriverError::new(
ErrorStatus::InvalidSessionId, "Tried to run command without establishing a connection",
));
}
} let conn = connection.as_mut().expect("Missing connection");
conn.send_command(&msg).map_err(|mut err| { // Shutdown the browser if no new session can be established // or the already existing session id is no longer valid. let is_new_session = matches!(msg.command, NewSession(_)); let invalid_session = msg.session_id.is_some()
&& err.error_code() == ErrorStatus::InvalidSessionId.error_code();
fn update(
&mutself,
msg: &WebDriverMessage<GeckoExtensionRoute>,
resp: &MarionetteResponse,
) -> WebDriverResult<()> { iflet NewSession(_) = msg.command { let session_id = try_opt!(
try_opt!(
resp.result.get("sessionId"),
ErrorStatus::SessionNotCreated, "Unable to get session id"
)
.as_str(),
ErrorStatus::SessionNotCreated, "Unable to convert session id to string"
); self.session_id = session_id.to_string();
};
Ok(())
}
/// Converts a Marionette JSON response into a `WebElement`. /// /// Note that it currently coerces all chrome elements, web frames, and web /// windows also into web elements. This will change at a later point. fn to_web_element(&self, json_data: &Value) -> WebDriverResult<WebElement> { let data = try_opt!(
json_data.as_object(),
ErrorStatus::UnknownError, "Failed to convert data to an object"
);
let element = data.get(ELEMENT_KEY); let frame = data.get(FRAME_KEY); let window = data.get(WINDOW_KEY);
let value = try_opt!(
element.or(frame).or(window),
ErrorStatus::UnknownError, "Failed to extract web element from Marionette response"
); let id = try_opt!(
value.as_str(),
ErrorStatus::UnknownError, "Failed to convert web element reference value to string"
)
.to_string();
Ok(WebElement(id))
}
/// Converts a Marionette JSON response into a `ShadowRoot`. fn to_shadow_root(&self, json_data: &Value) -> WebDriverResult<ShadowRoot> { let data = try_opt!(
json_data.as_object(),
ErrorStatus::UnknownError, "Failed to convert data to an object"
);
let shadow_root = data.get(SHADOW_KEY);
let value = try_opt!(
shadow_root,
ErrorStatus::UnknownError, "Failed to extract shadow root from Marionette response"
); let id = try_opt!(
value.as_str(),
ErrorStatus::UnknownError, "Failed to convert shadow root reference value to string"
)
.to_string();
Ok(ShadowRoot(id))
}
Ok(match msg.command { // Everything that doesn't have a response value
Get(_)
| GoBack
| GoForward
| Refresh
| SetTimeouts(_)
| SwitchToWindow(_)
| SwitchToFrame(_)
| SwitchToParentFrame
| AddCookie(_)
| DeleteCookies
| DeleteCookie(_)
| DismissAlert
| AcceptAlert
| SendAlertText(_)
| ElementClick(_)
| ElementClear(_)
| ElementSendKeys(_, _)
| PerformActions(_)
| ReleaseActions => WebDriverResponse::Void, // Things that simply return the contents of the marionette "value" property
GetCurrentUrl
| GetTitle
| GetPageSource
| GetWindowHandle
| IsDisplayed(_)
| IsSelected(_)
| GetElementAttribute(_, _)
| GetElementProperty(_, _)
| GetCSSValue(_, _)
| GetElementText(_)
| GetElementTagName(_)
| GetComputedLabel(_)
| GetComputedRole(_)
| IsEnabled(_)
| ExecuteScript(_)
| ExecuteAsyncScript(_)
| GetAlertText
| TakeScreenshot
| Print(_)
| SetPermission(_)
| TakeElementScreenshot(_)
| GPCGetGlobalPrivacyControl
| GPCSetGlobalPrivacyControl(_)
| WebAuthnAddCredential(_, _)
| WebAuthnAddVirtualAuthenticator(_)
| WebAuthnGetCredentials(_)
| WebAuthnRemoveAllCredentials(_)
| WebAuthnRemoveCredential(_, _)
| WebAuthnRemoveVirtualAuthenticator(_)
| WebAuthnSetUserVerified(_, _) => {
WebDriverResponse::Generic(resp.into_value_response(true)?)
}
GetTimeouts => { let script = match try_opt!(
resp.result.get("script"),
ErrorStatus::UnknownError, "Missing field: script"
) {
Value::Null => None,
n => try_opt!(
Some(n.as_u64()),
ErrorStatus::UnknownError, "Failed to interpret script timeout duration as u64"
),
}; let page_load = match try_opt!(
resp.result.get("pageLoad"),
ErrorStatus::UnknownError, "Missing field: pageLoad"
) {
Value::Null => None,
n => try_opt!(
Some(n.as_u64()),
ErrorStatus::UnknownError, "Failed to interpret pageLoad timeout duration as u64"
),
}; let implicit = match try_opt!(
resp.result.get("implicit"),
ErrorStatus::UnknownError, "Missing field: implicit"
) {
Value::Null => None,
n => try_opt!(
Some(n.as_u64()),
ErrorStatus::UnknownError, "Failed to interpret implicit timeout duration as u64"
),
};
WebDriverResponse::Timeouts(TimeoutsResponse {
script,
page_load,
implicit,
})
}
Status => panic!("Got status command that should already have been handled"),
GetWindowHandles => WebDriverResponse::Generic(resp.into_value_response(false)?),
NewWindow(_) => { let handle: String = try_opt!(
try_opt!(
resp.result.get("handle"),
ErrorStatus::UnknownError, "Failed to find handle field"
)
.as_str(),
ErrorStatus::UnknownError, "Failed to interpret handle as string"
)
.into(); let typ: String = try_opt!(
try_opt!(
resp.result.get("type"),
ErrorStatus::UnknownError, "Failed to find type field"
)
.as_str(),
ErrorStatus::UnknownError, "Failed to interpret type as string"
)
.into();
WebDriverResponse::NewWindow(NewWindowResponse { handle, typ })
}
CloseWindow => { let data = try_opt!(
resp.result.as_array(),
ErrorStatus::UnknownError, "Failed to interpret value as array"
); let handles = data
.iter()
.map(|x| {
Ok(try_opt!(
x.as_str(),
ErrorStatus::UnknownError, "Failed to interpret window handle as string"
)
.to_owned())
})
.collect::<Result<Vec<_>, _>>()?;
WebDriverResponse::CloseWindow(CloseWindowResponse(handles))
}
GetElementRect(_) => { let x = try_opt!(
try_opt!(
resp.result.get("x"),
ErrorStatus::UnknownError, "Failed to find x field"
)
.as_f64(),
ErrorStatus::UnknownError, "Failed to interpret x as float"
);
let y = try_opt!(
try_opt!(
resp.result.get("y"),
ErrorStatus::UnknownError, "Failed to find y field"
)
.as_f64(),
ErrorStatus::UnknownError, "Failed to interpret y as float"
);
let width = try_opt!(
try_opt!(
resp.result.get("width"),
ErrorStatus::UnknownError, "Failed to find width field"
)
.as_f64(),
ErrorStatus::UnknownError, "Failed to interpret width as float"
);
let height = try_opt!(
try_opt!(
resp.result.get("height"),
ErrorStatus::UnknownError, "Failed to find height field"
)
.as_f64(),
ErrorStatus::UnknownError, "Failed to interpret width as float"
);
let rect = ElementRectResponse {
x,
y,
width,
height,
};
WebDriverResponse::ElementRect(rect)
}
FullscreenWindow | MinimizeWindow | MaximizeWindow | GetWindowRect
| SetWindowRect(_) => { let width = try_opt!(
try_opt!(
resp.result.get("width"),
ErrorStatus::UnknownError, "Failed to find width field"
)
.as_u64(),
ErrorStatus::UnknownError, "Failed to interpret width as positive integer"
);
let height = try_opt!(
try_opt!(
resp.result.get("height"),
ErrorStatus::UnknownError, "Failed to find heigenht field"
)
.as_u64(),
ErrorStatus::UnknownError, "Failed to interpret height as positive integer"
);
let x = try_opt!(
try_opt!(
resp.result.get("x"),
ErrorStatus::UnknownError, "Failed to find x field"
)
.as_i64(),
ErrorStatus::UnknownError, "Failed to interpret x as integer"
);
let y = try_opt!(
try_opt!(
resp.result.get("y"),
ErrorStatus::UnknownError, "Failed to find y field"
)
.as_i64(),
ErrorStatus::UnknownError, "Failed to interpret y as integer"
);
let rect = WindowRectResponse {
x: x as i32,
y: y as i32,
width: width as i32,
height: height as i32,
};
WebDriverResponse::WindowRect(rect)
}
GetCookies => { let cookies: Vec<Cookie> = serde_json::from_value(resp.result)?;
WebDriverResponse::Cookies(CookiesResponse(cookies))
}
GetNamedCookie(ref name) => { letmut cookies: Vec<Cookie> = serde_json::from_value(resp.result)?;
cookies.retain(|x| x.name == *name); let cookie = try_opt!(
cookies.pop(),
ErrorStatus::NoSuchCookie,
format!("No cookie with name {}", name)
);
WebDriverResponse::Cookie(CookieResponse(cookie))
}
FindElement(_) | FindElementElement(_, _) | FindShadowRootElement(_, _) => { let element = self.to_web_element(try_opt!(
resp.result.get("value"),
ErrorStatus::UnknownError, "Failed to find value field"
))?;
WebDriverResponse::Generic(ValueResponse(serde_json::to_value(element)?))
}
FindElements(_) | FindElementElements(_, _) | FindShadowRootElements(_, _) => { let element_vec = try_opt!(
resp.result.as_array(),
ErrorStatus::UnknownError, "Failed to interpret value as array"
); let elements = element_vec
.iter()
.map(|x| self.to_web_element(x))
.collect::<Result<Vec<_>, _>>()?;
// TODO(Henrik): How to remove unwrap?
WebDriverResponse::Generic(ValueResponse(Value::Array(
elements
.iter()
.map(|x| serde_json::to_value(x).unwrap())
.collect(),
)))
}
GetShadowRoot(_) => { let shadow_root = self.to_shadow_root(try_opt!(
resp.result.get("value"),
ErrorStatus::UnknownError, "Failed to find value field"
))?;
WebDriverResponse::Generic(ValueResponse(serde_json::to_value(shadow_root)?))
}
GetActiveElement => { let element = self.to_web_element(try_opt!(
resp.result.get("value"),
ErrorStatus::UnknownError, "Failed to find value field"
))?;
WebDriverResponse::Generic(ValueResponse(serde_json::to_value(element)?))
}
NewSession(_) => { let session_id = try_opt!(
try_opt!(
resp.result.get("sessionId"),
ErrorStatus::InvalidSessionId, "Failed to find sessionId field"
)
.as_str(),
ErrorStatus::InvalidSessionId, "sessionId is not a string"
);
letmut capabilities = try_opt!(
try_opt!(
resp.result.get("capabilities"),
ErrorStatus::UnknownError, "Failed to find capabilities field"
)
.as_object(),
ErrorStatus::UnknownError, "capabilities field is not an object"
)
.clone();
impl From<MarionetteError> for WebDriverError { fn from(error: MarionetteError) -> WebDriverError { let status = ErrorStatus::from(error.code); let message = error.message;
// Convert `str` to `Cow<'static, str>` let data = error
.data
.map(|map| map.into_iter().map(|(k, v)| (Cow::Owned(k), v)).collect());
fn handshake(stream: &mut TcpStream) -> WebDriverResult<MarionetteHandshake> { let resp = (match stream.read_timeout() {
Ok(timeout) => { // If platform supports changing the read timeout of the stream, // use a short one only for the handshake with Marionette. Don't // make it shorter as 1000ms to not fail on slow connections.
stream
.set_read_timeout(Some(time::Duration::from_millis(1000)))
.ok(); let data = MarionetteConnection::read_resp(stream);
stream.set_read_timeout(timeout).ok();
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.