/* 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, LocalBrowser, RemoteBrowser}; usecrate::build; usecrate::capabilities::{FirefoxCapabilities, FirefoxOptions, ProfileType}; usecrate::command::{
AddonInstallParameters, AddonPath, AddonUninstallParameters, GeckoContextParameters,
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::{
AuthenticatorParameters as MarionetteAuthenticatorParameters,
AuthenticatorTransport as MarionetteAuthenticatorTransport,
Command as MarionetteWebDriverCommand, CredentialParameters as MarionetteCredentialParameters,
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, Url as MarionetteUrl,
UserVerificationParameters as MarionetteUserVerificationParameters,
WebAuthnProtocol as MarionetteWebAuthnProtocol, WindowRect as MarionetteWindowRect,
}; use mozdevice::AndroidStorageInput; use serde::de::{self, Deserialize, Deserializer}; use serde::ser::{Serialize, Serializer}; use serde_json::{Map, Value}; use std::env; use std::fs; use std::io::prelude::*; use std::io::Error as IoError; use std::io::ErrorKind; use std::io::Result as IoResult; use std::net::{Shutdown, TcpListener, TcpStream}; use std::path::{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, 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, GetParameters, JavascriptCommandParameters, LocatorParameters,
NewSessionParameters, NewWindowParameters, PrintMargins, PrintOrientation, PrintPage,
PrintPageRange, PrintParameters, SendKeysParameters, SetPermissionDescriptor,
SetPermissionParameters, SetPermissionState, SwitchToFrameParameters, SwitchToWindowParameters,
TimeoutsParameters, UserVerificationParameters, WebAuthnProtocol, WindowRectParameters,
}; use webdriver::command::{WebDriverCommand, WebDriverMessage}; use webdriver::common::{
Cookie, CredentialParameters, Date, FrameId, LocatorStrategy, ShadowRoot, WebElement,
ELEMENT_KEY, FRAME_KEY, SHADOW_KEY, 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.profile_root.as_deref(),
)?)
} elseif !self.settings.connect_existing {
Browser::Local(LocalBrowser::new(
options,
marionette_port, self.settings.jsdebugger, 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 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))
}
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();
iflet Some(cmd) = try_convert_to_marionette_message(msg, browser)? { let req = Message::Incoming(Request(id, cmd));
MarionetteCommand::encode_msg(req)
} else { let (opt_name, opt_parameters) = match msg.command {
Status => panic!("Got status command that should already have been handled"),
NewSession(_) => { letmut data = Map::new(); for (k, v) in capabilities.iter() {
data.insert(k.to_string(), serde_json::to_value(v)?);
}
let name = try_opt!(
opt_name,
ErrorStatus::UnsupportedOperation, "Operation not supported"
); let parameters = opt_parameters.unwrap_or_else(|| Ok(Map::new()))?;
let req = MarionetteCommand::new(id, name.into(), parameters);
MarionetteCommand::encode_msg(req)
}
}
}
impl From<MarionetteError> for WebDriverError { fn from(error: MarionetteError) -> WebDriverError { let status = ErrorStatus::from(error.code); let message = error.message;
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();
let data = serde_json::from_str::<MarionetteHandshake>(&resp)?;
if data.application_type != "gecko" { return Err(WebDriverError::new(
ErrorStatus::UnknownError,
format!("Unrecognized application type {}", data.application_type),
));
}
if data.protocol != 3 { return Err(WebDriverError::new(
ErrorStatus::UnknownError,
format!( "Unsupported Marionette protocol version {}, required 3",
data.protocol
),
));
}
Ok(data)
}
fn close(self, wait_for_shutdown: bool) -> WebDriverResult<()> { // Save minidump files of potential crashes from the profile if requested. iflet Ok(path) = env::var("MINIDUMP_SAVE_PATH") { iflet Err(e) = self.save_minidumps(&path) {
error!( "Failed to save minidump files to the requested location: {}",
e
);
}
} else {
debug!("To store minidump files of Firefox crashes the MINIDUMP_SAVE_PATH environment variable needs to be set.");
}
// Check if the folder exists and not empty. if !minidumps_path.exists() || minidumps_path.read_dir()?.next().is_none() { return Ok(());
}
match std::fs::read_dir(&minidumps_path) {
Ok(entries) => { for result_entry in entries { let entry = result_entry?; let file_type = entry.file_type()?;
if file_type.is_dir() { continue;
}
let path = entry.path(); let extension = path
.extension()
.and_then(|ext| ext.to_str())
.map(|ext| ext.to_lowercase())
.unwrap_or(String::from(""));
// Copy only *.dmp and *.extra files. if extension == "dmp" || extension == "extra" { let dest_path = save_path.join(entry.file_name());
fs::copy(path, &dest_path)?;
for result_entry in std::fs::read_dir(&minidumps_path).unwrap() { let entry = result_entry.unwrap();
let path: PathBuf = entry.path(); let filename_from_path = path.file_stem().unwrap().to_str().unwrap(); if filename == filename_from_path { let extension = path.extension().and_then(|ext| ext.to_str()).unwrap();
if extension == "dmp" {
dmp_file_present = true;
}
#[test] fn test_copy_minidumps_with_non_minidumps_files() { let tmp_dir_profile = TempDir::new().unwrap(); let profile_path = tmp_dir_profile.path();
let minidumps_folder = create_minidump_folder(&profile_path);
// Create a folder. let test_folder_binding = profile_path.join("test"); let test_folder = test_folder_binding.as_path();
fs::create_dir(test_folder).unwrap();
// Create a file with non minidumps extension.
create_file(&minidumps_folder, "test.txt");
let tmp_dir_minidumps = TempDir::new().unwrap(); let minidumps_path = tmp_dir_minidumps.path();
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.