tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

marionette.rs (75113B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 use crate::browser::{Browser, LocalBrowser, RemoteBrowser};
      6 use crate::build;
      7 use crate::capabilities::{FirefoxCapabilities, FirefoxOptions, ProfileType};
      8 use crate::command::{
      9    AddonInstallParameters, AddonPath, AddonUninstallParameters, GeckoContextParameters,
     10    GeckoExtensionCommand, GeckoExtensionRoute,
     11 };
     12 use crate::logging;
     13 use marionette_rs::common::{
     14    Cookie as MarionetteCookie, Date as MarionetteDate, Frame as MarionetteFrame,
     15    Timeouts as MarionetteTimeouts, WebElement as MarionetteWebElement, Window,
     16 };
     17 use marionette_rs::marionette::AppStatus;
     18 use marionette_rs::message::{Command, Message, MessageId, Request};
     19 use marionette_rs::webdriver::{
     20    AuthenticatorParameters as MarionetteAuthenticatorParameters,
     21    AuthenticatorTransport as MarionetteAuthenticatorTransport,
     22    Command as MarionetteWebDriverCommand, CredentialParameters as MarionetteCredentialParameters,
     23    GlobalPrivacyControlParameters as MarionetteGlobalPrivacyControlParameters,
     24    Keys as MarionetteKeys, Locator as MarionetteLocator, NewWindow as MarionetteNewWindow,
     25    PrintMargins as MarionettePrintMargins, PrintOrientation as MarionettePrintOrientation,
     26    PrintPage as MarionettePrintPage, PrintPageRange as MarionettePrintPageRange,
     27    PrintParameters as MarionettePrintParameters, ScreenshotOptions, Script as MarionetteScript,
     28    Selector as MarionetteSelector, SetPermissionDescriptor as MarionetteSetPermissionDescriptor,
     29    SetPermissionParameters as MarionetteSetPermissionParameters,
     30    SetPermissionState as MarionetteSetPermissionState, Url as MarionetteUrl,
     31    UserVerificationParameters as MarionetteUserVerificationParameters,
     32    WebAuthnProtocol as MarionetteWebAuthnProtocol, WindowRect as MarionetteWindowRect,
     33 };
     34 use mozdevice::AndroidStorageInput;
     35 use serde::de::{self, Deserialize, Deserializer};
     36 use serde::ser::{Serialize, Serializer};
     37 use serde_json::{Map, Value};
     38 use std::borrow::Cow;
     39 use std::collections::BTreeMap;
     40 use std::env;
     41 use std::fs;
     42 use std::io::prelude::*;
     43 use std::io::Error as IoError;
     44 use std::io::ErrorKind;
     45 use std::io::Result as IoResult;
     46 use std::net::{Shutdown, TcpListener, TcpStream};
     47 use std::path::{Path, PathBuf};
     48 use std::sync::Mutex;
     49 use std::thread;
     50 use std::time;
     51 use url::{Host, Url};
     52 use webdriver::capabilities::BrowserCapabilities;
     53 use webdriver::command::WebDriverCommand::{
     54    AcceptAlert, AddCookie, CloseWindow, DeleteCookie, DeleteCookies, DeleteSession, DismissAlert,
     55    ElementClear, ElementClick, ElementSendKeys, ExecuteAsyncScript, ExecuteScript, Extension,
     56    FindElement, FindElementElement, FindElementElements, FindElements, FindShadowRootElement,
     57    FindShadowRootElements, FullscreenWindow, GPCGetGlobalPrivacyControl,
     58    GPCSetGlobalPrivacyControl, Get, GetActiveElement, GetAlertText, GetCSSValue, GetComputedLabel,
     59    GetComputedRole, GetCookies, GetCurrentUrl, GetElementAttribute, GetElementProperty,
     60    GetElementRect, GetElementTagName, GetElementText, GetNamedCookie, GetPageSource,
     61    GetShadowRoot, GetTimeouts, GetTitle, GetWindowHandle, GetWindowHandles, GetWindowRect, GoBack,
     62    GoForward, IsDisplayed, IsEnabled, IsSelected, MaximizeWindow, MinimizeWindow, NewSession,
     63    NewWindow, PerformActions, Print, Refresh, ReleaseActions, SendAlertText, SetPermission,
     64    SetTimeouts, SetWindowRect, Status, SwitchToFrame, SwitchToParentFrame, SwitchToWindow,
     65    TakeElementScreenshot, TakeScreenshot, WebAuthnAddCredential, WebAuthnAddVirtualAuthenticator,
     66    WebAuthnGetCredentials, WebAuthnRemoveAllCredentials, WebAuthnRemoveCredential,
     67    WebAuthnRemoveVirtualAuthenticator, WebAuthnSetUserVerified,
     68 };
     69 use webdriver::command::{
     70    ActionsParameters, AddCookieParameters, AuthenticatorParameters, AuthenticatorTransport,
     71    GetNamedCookieParameters, GetParameters, GlobalPrivacyControlParameters,
     72    JavascriptCommandParameters, LocatorParameters, NewSessionParameters, NewWindowParameters,
     73    PrintMargins, PrintOrientation, PrintPage, PrintPageRange, PrintParameters, SendKeysParameters,
     74    SetPermissionDescriptor, SetPermissionParameters, SetPermissionState, SwitchToFrameParameters,
     75    SwitchToWindowParameters, TimeoutsParameters, UserVerificationParameters, WebAuthnProtocol,
     76    WindowRectParameters,
     77 };
     78 use webdriver::command::{WebDriverCommand, WebDriverMessage};
     79 use webdriver::common::{
     80    Cookie, CredentialParameters, Date, FrameId, LocatorStrategy, ShadowRoot, WebElement,
     81    ELEMENT_KEY, FRAME_KEY, SHADOW_KEY, WINDOW_KEY,
     82 };
     83 use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult};
     84 use webdriver::response::{
     85    CloseWindowResponse, CookieResponse, CookiesResponse, ElementRectResponse, NewSessionResponse,
     86    NewWindowResponse, TimeoutsResponse, ValueResponse, WebDriverResponse, WindowRectResponse,
     87 };
     88 use webdriver::server::{Session, WebDriverHandler};
     89 use webdriver::{capabilities::CapabilitiesMatching, server::SessionTeardownKind};
     90 
     91 #[derive(Debug, PartialEq, Deserialize)]
     92 struct MarionetteHandshake {
     93    #[serde(rename = "marionetteProtocol")]
     94    protocol: u16,
     95    #[serde(rename = "applicationType")]
     96    application_type: String,
     97 }
     98 
     99 #[derive(Default)]
    100 pub(crate) struct MarionetteSettings {
    101    pub(crate) binary: Option<PathBuf>,
    102    pub(crate) profile_root: Option<PathBuf>,
    103    pub(crate) connect_existing: bool,
    104    pub(crate) host: String,
    105    pub(crate) port: Option<u16>,
    106    pub(crate) websocket_port: u16,
    107    pub(crate) allow_hosts: Vec<Host>,
    108    pub(crate) allow_origins: Vec<Url>,
    109    pub(crate) system_access: bool,
    110 
    111    /// Brings up the Browser Toolbox when starting Firefox,
    112    /// letting you debug internals.
    113    pub(crate) jsdebugger: bool,
    114 
    115    pub(crate) android_storage: AndroidStorageInput,
    116 }
    117 
    118 #[derive(Default)]
    119 pub(crate) struct MarionetteHandler {
    120    connection: Mutex<Option<MarionetteConnection>>,
    121    settings: MarionetteSettings,
    122 }
    123 
    124 impl MarionetteHandler {
    125    pub(crate) fn new(settings: MarionetteSettings) -> MarionetteHandler {
    126        MarionetteHandler {
    127            connection: Mutex::new(None),
    128            settings,
    129        }
    130    }
    131 
    132    fn create_connection(
    133        &self,
    134        session_id: Option<String>,
    135        new_session_parameters: &NewSessionParameters,
    136    ) -> WebDriverResult<MarionetteConnection> {
    137        let mut fx_capabilities = FirefoxCapabilities::new(self.settings.binary.as_ref());
    138        let (capabilities, options) = {
    139            let mut capabilities = new_session_parameters
    140                .match_browser(&mut fx_capabilities)?
    141                .ok_or_else(|| {
    142                    WebDriverError::new(
    143                        ErrorStatus::SessionNotCreated,
    144                        "Unable to find a matching set of capabilities",
    145                    )
    146                })?;
    147 
    148            let options = FirefoxOptions::from_capabilities(
    149                fx_capabilities.chosen_binary.clone(),
    150                &self.settings,
    151                &mut capabilities,
    152            )?;
    153            (capabilities, options)
    154        };
    155 
    156        if let Some(l) = options.log.level {
    157            logging::set_max_level(l);
    158        }
    159 
    160        let marionette_host = self.settings.host.to_owned();
    161        let marionette_port = match self.settings.port {
    162            Some(port) => port,
    163            None => {
    164                // If we're launching Firefox Desktop version 95 or later, and there's no port
    165                // specified, we can pass 0 as the port and later read it back from
    166                // the profile.
    167                let can_use_profile: bool = options.android.is_none()
    168                    && options.profile != ProfileType::Named
    169                    && !self.settings.connect_existing
    170                    && fx_capabilities
    171                        .browser_version(&capabilities)
    172                        .map(|opt_v| {
    173                            opt_v
    174                                .map(|v| {
    175                                    fx_capabilities
    176                                        .compare_browser_version(&v, ">=95")
    177                                        .unwrap_or(false)
    178                                })
    179                                .unwrap_or(false)
    180                        })
    181                        .unwrap_or(false);
    182                if can_use_profile {
    183                    0
    184                } else {
    185                    get_free_port(&marionette_host)?
    186                }
    187            }
    188        };
    189 
    190        let websocket_port = if options.use_websocket {
    191            Some(self.settings.websocket_port)
    192        } else {
    193            None
    194        };
    195 
    196        let browser = if options.android.is_some() {
    197            // TODO: support connecting to running Apps.  There's no real obstruction here,
    198            // just some details about port forwarding to work through.  We can't follow
    199            // `chromedriver` here since it uses an abstract socket rather than a TCP socket:
    200            // see bug 1240830 for thoughts on doing that for Marionette.
    201            if self.settings.connect_existing {
    202                return Err(WebDriverError::new(
    203                    ErrorStatus::SessionNotCreated,
    204                    "Cannot connect to an existing Android App yet",
    205                ));
    206            }
    207            Browser::Remote(RemoteBrowser::new(
    208                options,
    209                marionette_port,
    210                websocket_port,
    211                self.settings.system_access,
    212                self.settings.profile_root.as_deref(),
    213            )?)
    214        } else if !self.settings.connect_existing {
    215            Browser::Local(LocalBrowser::new(
    216                options,
    217                marionette_port,
    218                self.settings.jsdebugger,
    219                self.settings.system_access,
    220                self.settings.profile_root.as_deref(),
    221            )?)
    222        } else {
    223            Browser::Existing(marionette_port)
    224        };
    225        let session = MarionetteSession::new(session_id, capabilities);
    226        MarionetteConnection::new(marionette_host, browser, session)
    227    }
    228 
    229    fn close_connection(&mut self, wait_for_shutdown: bool) {
    230        if let Ok(connection) = self.connection.get_mut() {
    231            if let Some(conn) = connection.take() {
    232                if let Err(e) = conn.close(wait_for_shutdown) {
    233                    error!("Failed to close browser connection: {}", e)
    234                }
    235            }
    236        }
    237    }
    238 }
    239 
    240 impl WebDriverHandler<GeckoExtensionRoute> for MarionetteHandler {
    241    fn handle_command(
    242        &mut self,
    243        _: &Option<Session>,
    244        msg: WebDriverMessage<GeckoExtensionRoute>,
    245    ) -> WebDriverResult<WebDriverResponse> {
    246        // First handle the status message which doesn't actually require a marionette
    247        // connection or message
    248        if let Status = msg.command {
    249            let (ready, message) = self
    250                .connection
    251                .get_mut()
    252                .map(|ref connection| {
    253                    connection
    254                        .as_ref()
    255                        .map(|_| (false, "Session already started"))
    256                        .unwrap_or((true, ""))
    257                })
    258                .unwrap_or((false, "geckodriver internal error"));
    259            let mut value = Map::new();
    260            value.insert("ready".to_string(), Value::Bool(ready));
    261            value.insert("message".to_string(), Value::String(message.into()));
    262            return Ok(WebDriverResponse::Generic(ValueResponse(Value::Object(
    263                value,
    264            ))));
    265        }
    266 
    267        match self.connection.lock() {
    268            Ok(mut connection) => {
    269                if connection.is_none() {
    270                    if let NewSession(ref capabilities) = msg.command {
    271                        let conn = self.create_connection(msg.session_id.clone(), capabilities)?;
    272                        *connection = Some(conn);
    273                    } else {
    274                        return Err(WebDriverError::new(
    275                            ErrorStatus::InvalidSessionId,
    276                            "Tried to run command without establishing a connection",
    277                        ));
    278                    }
    279                }
    280                let conn = connection.as_mut().expect("Missing connection");
    281                conn.send_command(&msg).map_err(|mut err| {
    282                    // Shutdown the browser if no session can
    283                    // be established due to errors.
    284                    if let NewSession(_) = msg.command {
    285                        err.delete_session = true;
    286                    }
    287                    err
    288                })
    289            }
    290            Err(_) => Err(WebDriverError::new(
    291                ErrorStatus::UnknownError,
    292                "Failed to aquire Marionette connection",
    293            )),
    294        }
    295    }
    296 
    297    fn teardown_session(&mut self, kind: SessionTeardownKind) {
    298        let wait_for_shutdown = match kind {
    299            SessionTeardownKind::Deleted => true,
    300            SessionTeardownKind::NotDeleted => false,
    301        };
    302        self.close_connection(wait_for_shutdown);
    303    }
    304 }
    305 
    306 impl Drop for MarionetteHandler {
    307    fn drop(&mut self) {
    308        self.close_connection(false);
    309    }
    310 }
    311 
    312 struct MarionetteSession {
    313    session_id: String,
    314    capabilities: Map<String, Value>,
    315    command_id: MessageId,
    316 }
    317 
    318 impl MarionetteSession {
    319    fn new(session_id: Option<String>, capabilities: Map<String, Value>) -> MarionetteSession {
    320        let initital_id = session_id.unwrap_or_default();
    321        MarionetteSession {
    322            session_id: initital_id,
    323            capabilities,
    324            command_id: 0,
    325        }
    326    }
    327 
    328    fn update(
    329        &mut self,
    330        msg: &WebDriverMessage<GeckoExtensionRoute>,
    331        resp: &MarionetteResponse,
    332    ) -> WebDriverResult<()> {
    333        if let NewSession(_) = msg.command {
    334            let session_id = try_opt!(
    335                try_opt!(
    336                    resp.result.get("sessionId"),
    337                    ErrorStatus::SessionNotCreated,
    338                    "Unable to get session id"
    339                )
    340                .as_str(),
    341                ErrorStatus::SessionNotCreated,
    342                "Unable to convert session id to string"
    343            );
    344            self.session_id = session_id.to_string();
    345        };
    346        Ok(())
    347    }
    348 
    349    /// Converts a Marionette JSON response into a `WebElement`.
    350    ///
    351    /// Note that it currently coerces all chrome elements, web frames, and web
    352    /// windows also into web elements.  This will change at a later point.
    353    fn to_web_element(&self, json_data: &Value) -> WebDriverResult<WebElement> {
    354        let data = try_opt!(
    355            json_data.as_object(),
    356            ErrorStatus::UnknownError,
    357            "Failed to convert data to an object"
    358        );
    359 
    360        let element = data.get(ELEMENT_KEY);
    361        let frame = data.get(FRAME_KEY);
    362        let window = data.get(WINDOW_KEY);
    363 
    364        let value = try_opt!(
    365            element.or(frame).or(window),
    366            ErrorStatus::UnknownError,
    367            "Failed to extract web element from Marionette response"
    368        );
    369        let id = try_opt!(
    370            value.as_str(),
    371            ErrorStatus::UnknownError,
    372            "Failed to convert web element reference value to string"
    373        )
    374        .to_string();
    375        Ok(WebElement(id))
    376    }
    377 
    378    /// Converts a Marionette JSON response into a `ShadowRoot`.
    379    fn to_shadow_root(&self, json_data: &Value) -> WebDriverResult<ShadowRoot> {
    380        let data = try_opt!(
    381            json_data.as_object(),
    382            ErrorStatus::UnknownError,
    383            "Failed to convert data to an object"
    384        );
    385 
    386        let shadow_root = data.get(SHADOW_KEY);
    387 
    388        let value = try_opt!(
    389            shadow_root,
    390            ErrorStatus::UnknownError,
    391            "Failed to extract shadow root from Marionette response"
    392        );
    393        let id = try_opt!(
    394            value.as_str(),
    395            ErrorStatus::UnknownError,
    396            "Failed to convert shadow root reference value to string"
    397        )
    398        .to_string();
    399        Ok(ShadowRoot(id))
    400    }
    401 
    402    fn next_command_id(&mut self) -> MessageId {
    403        self.command_id += 1;
    404        self.command_id
    405    }
    406 
    407    fn response(
    408        &mut self,
    409        msg: &WebDriverMessage<GeckoExtensionRoute>,
    410        resp: MarionetteResponse,
    411    ) -> WebDriverResult<WebDriverResponse> {
    412        use self::GeckoExtensionCommand::*;
    413 
    414        if resp.id != self.command_id {
    415            return Err(WebDriverError::new(
    416                ErrorStatus::UnknownError,
    417                format!(
    418                    "Marionette responses arrived out of sequence, expected {}, got {}",
    419                    self.command_id, resp.id
    420                ),
    421            ));
    422        }
    423 
    424        if let Some(error) = resp.error {
    425            return Err(error.into());
    426        }
    427 
    428        self.update(msg, &resp)?;
    429 
    430        Ok(match msg.command {
    431            // Everything that doesn't have a response value
    432            Get(_)
    433            | GoBack
    434            | GoForward
    435            | Refresh
    436            | SetTimeouts(_)
    437            | SwitchToWindow(_)
    438            | SwitchToFrame(_)
    439            | SwitchToParentFrame
    440            | AddCookie(_)
    441            | DeleteCookies
    442            | DeleteCookie(_)
    443            | DismissAlert
    444            | AcceptAlert
    445            | SendAlertText(_)
    446            | ElementClick(_)
    447            | ElementClear(_)
    448            | ElementSendKeys(_, _)
    449            | PerformActions(_)
    450            | ReleaseActions => WebDriverResponse::Void,
    451            // Things that simply return the contents of the marionette "value" property
    452            GetCurrentUrl
    453            | GetTitle
    454            | GetPageSource
    455            | GetWindowHandle
    456            | IsDisplayed(_)
    457            | IsSelected(_)
    458            | GetElementAttribute(_, _)
    459            | GetElementProperty(_, _)
    460            | GetCSSValue(_, _)
    461            | GetElementText(_)
    462            | GetElementTagName(_)
    463            | GetComputedLabel(_)
    464            | GetComputedRole(_)
    465            | IsEnabled(_)
    466            | ExecuteScript(_)
    467            | ExecuteAsyncScript(_)
    468            | GetAlertText
    469            | TakeScreenshot
    470            | Print(_)
    471            | SetPermission(_)
    472            | TakeElementScreenshot(_)
    473            | GPCGetGlobalPrivacyControl
    474            | GPCSetGlobalPrivacyControl(_)
    475            | WebAuthnAddVirtualAuthenticator(_)
    476            | WebAuthnRemoveVirtualAuthenticator
    477            | WebAuthnAddCredential(_)
    478            | WebAuthnGetCredentials
    479            | WebAuthnRemoveCredential
    480            | WebAuthnRemoveAllCredentials
    481            | WebAuthnSetUserVerified(_) => {
    482                WebDriverResponse::Generic(resp.into_value_response(true)?)
    483            }
    484            GetTimeouts => {
    485                let script = match try_opt!(
    486                    resp.result.get("script"),
    487                    ErrorStatus::UnknownError,
    488                    "Missing field: script"
    489                ) {
    490                    Value::Null => None,
    491                    n => try_opt!(
    492                        Some(n.as_u64()),
    493                        ErrorStatus::UnknownError,
    494                        "Failed to interpret script timeout duration as u64"
    495                    ),
    496                };
    497                let page_load = try_opt!(
    498                    try_opt!(
    499                        resp.result.get("pageLoad"),
    500                        ErrorStatus::UnknownError,
    501                        "Missing field: pageLoad"
    502                    )
    503                    .as_u64(),
    504                    ErrorStatus::UnknownError,
    505                    "Failed to interpret page load duration as u64"
    506                );
    507                let implicit = try_opt!(
    508                    try_opt!(
    509                        resp.result.get("implicit"),
    510                        ErrorStatus::UnknownError,
    511                        "Missing field: implicit"
    512                    )
    513                    .as_u64(),
    514                    ErrorStatus::UnknownError,
    515                    "Failed to interpret implicit search duration as u64"
    516                );
    517 
    518                WebDriverResponse::Timeouts(TimeoutsResponse {
    519                    script,
    520                    page_load,
    521                    implicit,
    522                })
    523            }
    524            Status => panic!("Got status command that should already have been handled"),
    525            GetWindowHandles => WebDriverResponse::Generic(resp.into_value_response(false)?),
    526            NewWindow(_) => {
    527                let handle: String = try_opt!(
    528                    try_opt!(
    529                        resp.result.get("handle"),
    530                        ErrorStatus::UnknownError,
    531                        "Failed to find handle field"
    532                    )
    533                    .as_str(),
    534                    ErrorStatus::UnknownError,
    535                    "Failed to interpret handle as string"
    536                )
    537                .into();
    538                let typ: String = try_opt!(
    539                    try_opt!(
    540                        resp.result.get("type"),
    541                        ErrorStatus::UnknownError,
    542                        "Failed to find type field"
    543                    )
    544                    .as_str(),
    545                    ErrorStatus::UnknownError,
    546                    "Failed to interpret type as string"
    547                )
    548                .into();
    549 
    550                WebDriverResponse::NewWindow(NewWindowResponse { handle, typ })
    551            }
    552            CloseWindow => {
    553                let data = try_opt!(
    554                    resp.result.as_array(),
    555                    ErrorStatus::UnknownError,
    556                    "Failed to interpret value as array"
    557                );
    558                let handles = data
    559                    .iter()
    560                    .map(|x| {
    561                        Ok(try_opt!(
    562                            x.as_str(),
    563                            ErrorStatus::UnknownError,
    564                            "Failed to interpret window handle as string"
    565                        )
    566                        .to_owned())
    567                    })
    568                    .collect::<Result<Vec<_>, _>>()?;
    569                WebDriverResponse::CloseWindow(CloseWindowResponse(handles))
    570            }
    571            GetElementRect(_) => {
    572                let x = try_opt!(
    573                    try_opt!(
    574                        resp.result.get("x"),
    575                        ErrorStatus::UnknownError,
    576                        "Failed to find x field"
    577                    )
    578                    .as_f64(),
    579                    ErrorStatus::UnknownError,
    580                    "Failed to interpret x as float"
    581                );
    582 
    583                let y = try_opt!(
    584                    try_opt!(
    585                        resp.result.get("y"),
    586                        ErrorStatus::UnknownError,
    587                        "Failed to find y field"
    588                    )
    589                    .as_f64(),
    590                    ErrorStatus::UnknownError,
    591                    "Failed to interpret y as float"
    592                );
    593 
    594                let width = try_opt!(
    595                    try_opt!(
    596                        resp.result.get("width"),
    597                        ErrorStatus::UnknownError,
    598                        "Failed to find width field"
    599                    )
    600                    .as_f64(),
    601                    ErrorStatus::UnknownError,
    602                    "Failed to interpret width as float"
    603                );
    604 
    605                let height = try_opt!(
    606                    try_opt!(
    607                        resp.result.get("height"),
    608                        ErrorStatus::UnknownError,
    609                        "Failed to find height field"
    610                    )
    611                    .as_f64(),
    612                    ErrorStatus::UnknownError,
    613                    "Failed to interpret width as float"
    614                );
    615 
    616                let rect = ElementRectResponse {
    617                    x,
    618                    y,
    619                    width,
    620                    height,
    621                };
    622                WebDriverResponse::ElementRect(rect)
    623            }
    624            FullscreenWindow | MinimizeWindow | MaximizeWindow | GetWindowRect
    625            | SetWindowRect(_) => {
    626                let width = try_opt!(
    627                    try_opt!(
    628                        resp.result.get("width"),
    629                        ErrorStatus::UnknownError,
    630                        "Failed to find width field"
    631                    )
    632                    .as_u64(),
    633                    ErrorStatus::UnknownError,
    634                    "Failed to interpret width as positive integer"
    635                );
    636 
    637                let height = try_opt!(
    638                    try_opt!(
    639                        resp.result.get("height"),
    640                        ErrorStatus::UnknownError,
    641                        "Failed to find heigenht field"
    642                    )
    643                    .as_u64(),
    644                    ErrorStatus::UnknownError,
    645                    "Failed to interpret height as positive integer"
    646                );
    647 
    648                let x = try_opt!(
    649                    try_opt!(
    650                        resp.result.get("x"),
    651                        ErrorStatus::UnknownError,
    652                        "Failed to find x field"
    653                    )
    654                    .as_i64(),
    655                    ErrorStatus::UnknownError,
    656                    "Failed to interpret x as integer"
    657                );
    658 
    659                let y = try_opt!(
    660                    try_opt!(
    661                        resp.result.get("y"),
    662                        ErrorStatus::UnknownError,
    663                        "Failed to find y field"
    664                    )
    665                    .as_i64(),
    666                    ErrorStatus::UnknownError,
    667                    "Failed to interpret y as integer"
    668                );
    669 
    670                let rect = WindowRectResponse {
    671                    x: x as i32,
    672                    y: y as i32,
    673                    width: width as i32,
    674                    height: height as i32,
    675                };
    676                WebDriverResponse::WindowRect(rect)
    677            }
    678            GetCookies => {
    679                let cookies: Vec<Cookie> = serde_json::from_value(resp.result)?;
    680                WebDriverResponse::Cookies(CookiesResponse(cookies))
    681            }
    682            GetNamedCookie(ref name) => {
    683                let mut cookies: Vec<Cookie> = serde_json::from_value(resp.result)?;
    684                cookies.retain(|x| x.name == *name);
    685                let cookie = try_opt!(
    686                    cookies.pop(),
    687                    ErrorStatus::NoSuchCookie,
    688                    format!("No cookie with name {}", name)
    689                );
    690                WebDriverResponse::Cookie(CookieResponse(cookie))
    691            }
    692            FindElement(_) | FindElementElement(_, _) | FindShadowRootElement(_, _) => {
    693                let element = self.to_web_element(try_opt!(
    694                    resp.result.get("value"),
    695                    ErrorStatus::UnknownError,
    696                    "Failed to find value field"
    697                ))?;
    698                WebDriverResponse::Generic(ValueResponse(serde_json::to_value(element)?))
    699            }
    700            FindElements(_) | FindElementElements(_, _) | FindShadowRootElements(_, _) => {
    701                let element_vec = try_opt!(
    702                    resp.result.as_array(),
    703                    ErrorStatus::UnknownError,
    704                    "Failed to interpret value as array"
    705                );
    706                let elements = element_vec
    707                    .iter()
    708                    .map(|x| self.to_web_element(x))
    709                    .collect::<Result<Vec<_>, _>>()?;
    710 
    711                // TODO(Henrik): How to remove unwrap?
    712                WebDriverResponse::Generic(ValueResponse(Value::Array(
    713                    elements
    714                        .iter()
    715                        .map(|x| serde_json::to_value(x).unwrap())
    716                        .collect(),
    717                )))
    718            }
    719            GetShadowRoot(_) => {
    720                let shadow_root = self.to_shadow_root(try_opt!(
    721                    resp.result.get("value"),
    722                    ErrorStatus::UnknownError,
    723                    "Failed to find value field"
    724                ))?;
    725                WebDriverResponse::Generic(ValueResponse(serde_json::to_value(shadow_root)?))
    726            }
    727            GetActiveElement => {
    728                let element = self.to_web_element(try_opt!(
    729                    resp.result.get("value"),
    730                    ErrorStatus::UnknownError,
    731                    "Failed to find value field"
    732                ))?;
    733                WebDriverResponse::Generic(ValueResponse(serde_json::to_value(element)?))
    734            }
    735            NewSession(_) => {
    736                let session_id = try_opt!(
    737                    try_opt!(
    738                        resp.result.get("sessionId"),
    739                        ErrorStatus::InvalidSessionId,
    740                        "Failed to find sessionId field"
    741                    )
    742                    .as_str(),
    743                    ErrorStatus::InvalidSessionId,
    744                    "sessionId is not a string"
    745                );
    746 
    747                let mut capabilities = try_opt!(
    748                    try_opt!(
    749                        resp.result.get("capabilities"),
    750                        ErrorStatus::UnknownError,
    751                        "Failed to find capabilities field"
    752                    )
    753                    .as_object(),
    754                    ErrorStatus::UnknownError,
    755                    "capabilities field is not an object"
    756                )
    757                .clone();
    758 
    759                capabilities.insert("moz:geckodriverVersion".into(), build::build_info().into());
    760 
    761                WebDriverResponse::NewSession(NewSessionResponse::new(
    762                    session_id.to_string(),
    763                    Value::Object(capabilities),
    764                ))
    765            }
    766            DeleteSession => WebDriverResponse::DeleteSession,
    767            Extension(ref extension) => match extension {
    768                GetContext => WebDriverResponse::Generic(resp.into_value_response(true)?),
    769                SetContext(_) => WebDriverResponse::Void,
    770                InstallAddon(_) => WebDriverResponse::Generic(resp.into_value_response(true)?),
    771                UninstallAddon(_) => WebDriverResponse::Void,
    772                TakeFullScreenshot => WebDriverResponse::Generic(resp.into_value_response(true)?),
    773            },
    774        })
    775    }
    776 }
    777 
    778 fn try_convert_to_marionette_message(
    779    msg: &WebDriverMessage<GeckoExtensionRoute>,
    780    browser: &Browser,
    781 ) -> WebDriverResult<Option<Command>> {
    782    use self::GeckoExtensionCommand::*;
    783    use self::WebDriverCommand::*;
    784 
    785    Ok(match msg.command {
    786        AcceptAlert => Some(Command::WebDriver(MarionetteWebDriverCommand::AcceptAlert)),
    787        AddCookie(ref x) => Some(Command::WebDriver(MarionetteWebDriverCommand::AddCookie(
    788            x.to_marionette()?,
    789        ))),
    790        CloseWindow => Some(Command::WebDriver(MarionetteWebDriverCommand::CloseWindow)),
    791        DeleteCookie(ref x) => Some(Command::WebDriver(
    792            MarionetteWebDriverCommand::DeleteCookie(x.clone()),
    793        )),
    794        DeleteCookies => Some(Command::WebDriver(
    795            MarionetteWebDriverCommand::DeleteCookies,
    796        )),
    797        DeleteSession => match browser {
    798            Browser::Local(_) | Browser::Remote(_) => Some(Command::Marionette(
    799                marionette_rs::marionette::Command::DeleteSession {
    800                    flags: vec![AppStatus::eForceQuit],
    801                },
    802            )),
    803            Browser::Existing(_) => Some(Command::WebDriver(
    804                MarionetteWebDriverCommand::DeleteSession,
    805            )),
    806        },
    807        DismissAlert => Some(Command::WebDriver(MarionetteWebDriverCommand::DismissAlert)),
    808        ElementClear(ref e) => Some(Command::WebDriver(
    809            MarionetteWebDriverCommand::ElementClear {
    810                id: e.clone().to_string(),
    811            },
    812        )),
    813        ElementClick(ref e) => Some(Command::WebDriver(
    814            MarionetteWebDriverCommand::ElementClick {
    815                id: e.clone().to_string(),
    816            },
    817        )),
    818        ElementSendKeys(ref e, ref x) => {
    819            let keys = x.to_marionette()?;
    820            Some(Command::WebDriver(
    821                MarionetteWebDriverCommand::ElementSendKeys {
    822                    id: e.clone().to_string(),
    823                    text: keys.text.clone(),
    824                    value: keys.value,
    825                },
    826            ))
    827        }
    828        ExecuteAsyncScript(ref x) => Some(Command::WebDriver(
    829            MarionetteWebDriverCommand::ExecuteAsyncScript(x.to_marionette()?),
    830        )),
    831        ExecuteScript(ref x) => Some(Command::WebDriver(
    832            MarionetteWebDriverCommand::ExecuteScript(x.to_marionette()?),
    833        )),
    834        FindElement(ref x) => Some(Command::WebDriver(MarionetteWebDriverCommand::FindElement(
    835            x.to_marionette()?,
    836        ))),
    837        FindElements(ref x) => Some(Command::WebDriver(
    838            MarionetteWebDriverCommand::FindElements(x.to_marionette()?),
    839        )),
    840        FindElementElement(ref e, ref x) => {
    841            let locator = x.to_marionette()?;
    842            Some(Command::WebDriver(MarionetteWebDriverCommand::FindElement(
    843                MarionetteLocator {
    844                    element: Some(e.clone().to_string()),
    845                    using: locator.using,
    846                    value: locator.value,
    847                },
    848            )))
    849        }
    850        FindElementElements(ref e, ref x) => {
    851            let locator = x.to_marionette()?;
    852            Some(Command::WebDriver(
    853                MarionetteWebDriverCommand::FindElements(MarionetteLocator {
    854                    element: Some(e.clone().to_string()),
    855                    using: locator.using,
    856                    value: locator.value,
    857                }),
    858            ))
    859        }
    860        FindShadowRootElement(ref s, ref x) => {
    861            let locator = x.to_marionette()?;
    862            Some(Command::WebDriver(
    863                MarionetteWebDriverCommand::FindShadowRootElement {
    864                    shadow_root: s.clone().to_string(),
    865                    using: locator.using,
    866                    value: locator.value,
    867                },
    868            ))
    869        }
    870        FindShadowRootElements(ref s, ref x) => {
    871            let locator = x.to_marionette()?;
    872            Some(Command::WebDriver(
    873                MarionetteWebDriverCommand::FindShadowRootElements {
    874                    shadow_root: s.clone().to_string(),
    875                    using: locator.using.clone(),
    876                    value: locator.value,
    877                },
    878            ))
    879        }
    880        FullscreenWindow => Some(Command::WebDriver(
    881            MarionetteWebDriverCommand::FullscreenWindow,
    882        )),
    883        Get(ref x) => Some(Command::WebDriver(MarionetteWebDriverCommand::Get(
    884            x.to_marionette()?,
    885        ))),
    886        GetActiveElement => Some(Command::WebDriver(
    887            MarionetteWebDriverCommand::GetActiveElement,
    888        )),
    889        GetAlertText => Some(Command::WebDriver(MarionetteWebDriverCommand::GetAlertText)),
    890        GetComputedLabel(ref e) => Some(Command::WebDriver(
    891            MarionetteWebDriverCommand::GetComputedLabel {
    892                id: e.clone().to_string(),
    893            },
    894        )),
    895        GetComputedRole(ref e) => Some(Command::WebDriver(
    896            MarionetteWebDriverCommand::GetComputedRole {
    897                id: e.clone().to_string(),
    898            },
    899        )),
    900        GetCookies | GetNamedCookie(_) => {
    901            Some(Command::WebDriver(MarionetteWebDriverCommand::GetCookies))
    902        }
    903        GetCSSValue(ref e, ref x) => Some(Command::WebDriver(
    904            MarionetteWebDriverCommand::GetCSSValue {
    905                id: e.clone().to_string(),
    906                property: x.clone(),
    907            },
    908        )),
    909        GetCurrentUrl => Some(Command::WebDriver(
    910            MarionetteWebDriverCommand::GetCurrentUrl,
    911        )),
    912        GetElementAttribute(ref e, ref x) => Some(Command::WebDriver(
    913            MarionetteWebDriverCommand::GetElementAttribute {
    914                id: e.clone().to_string(),
    915                name: x.clone(),
    916            },
    917        )),
    918        GetElementProperty(ref e, ref x) => Some(Command::WebDriver(
    919            MarionetteWebDriverCommand::GetElementProperty {
    920                id: e.clone().to_string(),
    921                name: x.clone(),
    922            },
    923        )),
    924        GetElementRect(ref e) => Some(Command::WebDriver(
    925            MarionetteWebDriverCommand::GetElementRect {
    926                id: e.clone().to_string(),
    927            },
    928        )),
    929        GetElementTagName(ref e) => Some(Command::WebDriver(
    930            MarionetteWebDriverCommand::GetElementTagName {
    931                id: e.clone().to_string(),
    932            },
    933        )),
    934        GetElementText(ref e) => Some(Command::WebDriver(
    935            MarionetteWebDriverCommand::GetElementText {
    936                id: e.clone().to_string(),
    937            },
    938        )),
    939        GetPageSource => Some(Command::WebDriver(
    940            MarionetteWebDriverCommand::GetPageSource,
    941        )),
    942        GetShadowRoot(ref e) => Some(Command::WebDriver(
    943            MarionetteWebDriverCommand::GetShadowRoot {
    944                id: e.clone().to_string(),
    945            },
    946        )),
    947        GetTitle => Some(Command::WebDriver(MarionetteWebDriverCommand::GetTitle)),
    948        GetWindowHandle => Some(Command::WebDriver(
    949            MarionetteWebDriverCommand::GetWindowHandle,
    950        )),
    951        GetWindowHandles => Some(Command::WebDriver(
    952            MarionetteWebDriverCommand::GetWindowHandles,
    953        )),
    954        GetWindowRect => Some(Command::WebDriver(
    955            MarionetteWebDriverCommand::GetWindowRect,
    956        )),
    957        GetTimeouts => Some(Command::WebDriver(MarionetteWebDriverCommand::GetTimeouts)),
    958        GoBack => Some(Command::WebDriver(MarionetteWebDriverCommand::GoBack)),
    959        GoForward => Some(Command::WebDriver(MarionetteWebDriverCommand::GoForward)),
    960        IsDisplayed(ref e) => Some(Command::WebDriver(
    961            MarionetteWebDriverCommand::IsDisplayed {
    962                id: e.clone().to_string(),
    963            },
    964        )),
    965        IsEnabled(ref e) => Some(Command::WebDriver(MarionetteWebDriverCommand::IsEnabled {
    966            id: e.clone().to_string(),
    967        })),
    968        IsSelected(ref e) => Some(Command::WebDriver(MarionetteWebDriverCommand::IsSelected {
    969            id: e.clone().to_string(),
    970        })),
    971        MaximizeWindow => Some(Command::WebDriver(
    972            MarionetteWebDriverCommand::MaximizeWindow,
    973        )),
    974        MinimizeWindow => Some(Command::WebDriver(
    975            MarionetteWebDriverCommand::MinimizeWindow,
    976        )),
    977        NewWindow(ref x) => Some(Command::WebDriver(MarionetteWebDriverCommand::NewWindow(
    978            x.to_marionette()?,
    979        ))),
    980        Print(ref x) => Some(Command::WebDriver(MarionetteWebDriverCommand::Print(
    981            x.to_marionette()?,
    982        ))),
    983        GPCGetGlobalPrivacyControl => Some(Command::WebDriver(
    984            MarionetteWebDriverCommand::GPCGetGlobalPrivacyControl,
    985        )),
    986        GPCSetGlobalPrivacyControl(ref x) => Some(Command::WebDriver(
    987            MarionetteWebDriverCommand::GPCSetGlobalPrivacyControl(x.to_marionette()?),
    988        )),
    989        WebAuthnAddVirtualAuthenticator(ref x) => Some(Command::WebDriver(
    990            MarionetteWebDriverCommand::WebAuthnAddVirtualAuthenticator(x.to_marionette()?),
    991        )),
    992        WebAuthnRemoveVirtualAuthenticator => Some(Command::WebDriver(
    993            MarionetteWebDriverCommand::WebAuthnRemoveVirtualAuthenticator,
    994        )),
    995        WebAuthnAddCredential(ref x) => Some(Command::WebDriver(
    996            MarionetteWebDriverCommand::WebAuthnAddCredential(x.to_marionette()?),
    997        )),
    998        WebAuthnGetCredentials => Some(Command::WebDriver(
    999            MarionetteWebDriverCommand::WebAuthnGetCredentials,
   1000        )),
   1001        WebAuthnRemoveCredential => Some(Command::WebDriver(
   1002            MarionetteWebDriverCommand::WebAuthnRemoveCredential,
   1003        )),
   1004        WebAuthnRemoveAllCredentials => Some(Command::WebDriver(
   1005            MarionetteWebDriverCommand::WebAuthnRemoveAllCredentials,
   1006        )),
   1007        WebAuthnSetUserVerified(ref x) => Some(Command::WebDriver(
   1008            MarionetteWebDriverCommand::WebAuthnSetUserVerified(x.to_marionette()?),
   1009        )),
   1010        Refresh => Some(Command::WebDriver(MarionetteWebDriverCommand::Refresh)),
   1011        ReleaseActions => Some(Command::WebDriver(
   1012            MarionetteWebDriverCommand::ReleaseActions,
   1013        )),
   1014        SendAlertText(ref x) => Some(Command::WebDriver(
   1015            MarionetteWebDriverCommand::SendAlertText(x.to_marionette()?),
   1016        )),
   1017        SetPermission(ref x) => Some(Command::WebDriver(
   1018            MarionetteWebDriverCommand::SetPermission(x.to_marionette()?),
   1019        )),
   1020        SetTimeouts(ref x) => Some(Command::WebDriver(MarionetteWebDriverCommand::SetTimeouts(
   1021            x.to_marionette()?,
   1022        ))),
   1023        SetWindowRect(ref x) => Some(Command::WebDriver(
   1024            MarionetteWebDriverCommand::SetWindowRect(x.to_marionette()?),
   1025        )),
   1026        SwitchToFrame(ref x) => Some(Command::WebDriver(
   1027            MarionetteWebDriverCommand::SwitchToFrame(x.to_marionette()?),
   1028        )),
   1029        SwitchToParentFrame => Some(Command::WebDriver(
   1030            MarionetteWebDriverCommand::SwitchToParentFrame,
   1031        )),
   1032        SwitchToWindow(ref x) => Some(Command::WebDriver(
   1033            MarionetteWebDriverCommand::SwitchToWindow(x.to_marionette()?),
   1034        )),
   1035        TakeElementScreenshot(ref e) => {
   1036            let screenshot = ScreenshotOptions {
   1037                id: Some(e.clone().to_string()),
   1038                highlights: vec![],
   1039                full: false,
   1040            };
   1041            Some(Command::WebDriver(
   1042                MarionetteWebDriverCommand::TakeScreenshot(screenshot),
   1043            ))
   1044        }
   1045        TakeScreenshot => {
   1046            let screenshot = ScreenshotOptions {
   1047                id: None,
   1048                highlights: vec![],
   1049                full: false,
   1050            };
   1051            Some(Command::WebDriver(
   1052                MarionetteWebDriverCommand::TakeScreenshot(screenshot),
   1053            ))
   1054        }
   1055        Extension(TakeFullScreenshot) => {
   1056            let screenshot = ScreenshotOptions {
   1057                id: None,
   1058                highlights: vec![],
   1059                full: true,
   1060            };
   1061            Some(Command::WebDriver(
   1062                MarionetteWebDriverCommand::TakeScreenshot(screenshot),
   1063            ))
   1064        }
   1065        _ => None,
   1066    })
   1067 }
   1068 
   1069 #[derive(Debug, PartialEq)]
   1070 struct MarionetteCommand {
   1071    id: MessageId,
   1072    name: String,
   1073    params: Map<String, Value>,
   1074 }
   1075 
   1076 impl Serialize for MarionetteCommand {
   1077    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
   1078    where
   1079        S: Serializer,
   1080    {
   1081        let data = (&0, &self.id, &self.name, &self.params);
   1082        data.serialize(serializer)
   1083    }
   1084 }
   1085 
   1086 impl MarionetteCommand {
   1087    fn new(id: MessageId, name: String, params: Map<String, Value>) -> MarionetteCommand {
   1088        MarionetteCommand { id, name, params }
   1089    }
   1090 
   1091    fn encode_msg<T>(msg: T) -> WebDriverResult<String>
   1092    where
   1093        T: serde::Serialize,
   1094    {
   1095        let data = serde_json::to_string(&msg)?;
   1096 
   1097        Ok(format!("{}:{}", data.len(), data))
   1098    }
   1099 
   1100    fn from_webdriver_message(
   1101        id: MessageId,
   1102        capabilities: &Map<String, Value>,
   1103        browser: &Browser,
   1104        msg: &WebDriverMessage<GeckoExtensionRoute>,
   1105    ) -> WebDriverResult<String> {
   1106        use self::GeckoExtensionCommand::*;
   1107 
   1108        if let Some(cmd) = try_convert_to_marionette_message(msg, browser)? {
   1109            let req = Message::Incoming(Request(id, cmd));
   1110            MarionetteCommand::encode_msg(req)
   1111        } else {
   1112            let (opt_name, opt_parameters) = match msg.command {
   1113                Status => panic!("Got status command that should already have been handled"),
   1114                NewSession(_) => {
   1115                    let mut data = Map::new();
   1116                    for (k, v) in capabilities.iter() {
   1117                        data.insert(k.to_string(), serde_json::to_value(v)?);
   1118                    }
   1119 
   1120                    (Some("WebDriver:NewSession"), Some(Ok(data)))
   1121                }
   1122                PerformActions(ref x) => {
   1123                    (Some("WebDriver:PerformActions"), Some(x.to_marionette()))
   1124                }
   1125                Extension(ref extension) => match extension {
   1126                    GetContext => (Some("Marionette:GetContext"), None),
   1127                    InstallAddon(x) => match x {
   1128                        AddonInstallParameters::AddonBase64(data) => {
   1129                            let addon = AddonPath {
   1130                                path: browser.create_file(&data.addon)?,
   1131                                temporary: data.temporary,
   1132                                allow_private_browsing: data.allow_private_browsing,
   1133                            };
   1134                            (Some("Addon:Install"), Some(addon.to_marionette()))
   1135                        }
   1136                        AddonInstallParameters::AddonPath(data) => {
   1137                            (Some("Addon:Install"), Some(data.to_marionette()))
   1138                        }
   1139                    },
   1140                    SetContext(x) => (Some("Marionette:SetContext"), Some(x.to_marionette())),
   1141                    UninstallAddon(x) => (Some("Addon:Uninstall"), Some(x.to_marionette())),
   1142                    _ => (None, None),
   1143                },
   1144                _ => (None, None),
   1145            };
   1146 
   1147            let name = try_opt!(
   1148                opt_name,
   1149                ErrorStatus::UnsupportedOperation,
   1150                "Operation not supported"
   1151            );
   1152            let parameters = opt_parameters.unwrap_or_else(|| Ok(Map::new()))?;
   1153 
   1154            let req = MarionetteCommand::new(id, name.into(), parameters);
   1155            MarionetteCommand::encode_msg(req)
   1156        }
   1157    }
   1158 }
   1159 
   1160 #[derive(Debug, PartialEq)]
   1161 struct MarionetteResponse {
   1162    id: MessageId,
   1163    error: Option<MarionetteError>,
   1164    result: Value,
   1165 }
   1166 
   1167 impl<'de> Deserialize<'de> for MarionetteResponse {
   1168    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   1169    where
   1170        D: Deserializer<'de>,
   1171    {
   1172        #[derive(Deserialize)]
   1173        struct ResponseWrapper {
   1174            msg_type: u64,
   1175            id: MessageId,
   1176            error: Option<MarionetteError>,
   1177            result: Value,
   1178        }
   1179 
   1180        let wrapper: ResponseWrapper = Deserialize::deserialize(deserializer)?;
   1181 
   1182        if wrapper.msg_type != 1 {
   1183            return Err(de::Error::custom(
   1184                "Expected '1' in first element of response",
   1185            ));
   1186        };
   1187 
   1188        Ok(MarionetteResponse {
   1189            id: wrapper.id,
   1190            error: wrapper.error,
   1191            result: wrapper.result,
   1192        })
   1193    }
   1194 }
   1195 
   1196 impl MarionetteResponse {
   1197    fn into_value_response(self, value_required: bool) -> WebDriverResult<ValueResponse> {
   1198        let value: &Value = if value_required {
   1199            try_opt!(
   1200                self.result.get("value"),
   1201                ErrorStatus::UnknownError,
   1202                "Failed to find value field"
   1203            )
   1204        } else {
   1205            &self.result
   1206        };
   1207 
   1208        Ok(ValueResponse(value.clone()))
   1209    }
   1210 }
   1211 
   1212 #[derive(Debug, PartialEq, Serialize, Deserialize)]
   1213 struct MarionetteError {
   1214    #[serde(rename = "error")]
   1215    code: String,
   1216    message: String,
   1217    data: Option<BTreeMap<String, Value>>,
   1218    stacktrace: Option<String>,
   1219 }
   1220 
   1221 impl From<MarionetteError> for WebDriverError {
   1222    fn from(error: MarionetteError) -> WebDriverError {
   1223        let status = ErrorStatus::from(error.code);
   1224        let message = error.message;
   1225 
   1226        // Convert `str` to `Cow<'static, str>`
   1227        let data = error
   1228            .data
   1229            .map(|map| map.into_iter().map(|(k, v)| (Cow::Owned(k), v)).collect());
   1230 
   1231        WebDriverError::new_with_data(status, message, data, error.stacktrace)
   1232    }
   1233 }
   1234 
   1235 fn get_free_port(host: &str) -> IoResult<u16> {
   1236    TcpListener::bind((host, 0))
   1237        .and_then(|stream| stream.local_addr())
   1238        .map(|x| x.port())
   1239 }
   1240 
   1241 struct MarionetteConnection {
   1242    browser: Browser,
   1243    session: MarionetteSession,
   1244    stream: TcpStream,
   1245 }
   1246 
   1247 impl MarionetteConnection {
   1248    fn new(
   1249        host: String,
   1250        mut browser: Browser,
   1251        session: MarionetteSession,
   1252    ) -> WebDriverResult<MarionetteConnection> {
   1253        let stream = match MarionetteConnection::connect(&host, &mut browser) {
   1254            Ok(stream) => stream,
   1255            Err(e) => {
   1256                if let Err(e) = browser.close(true) {
   1257                    error!("Failed to stop browser: {:?}", e);
   1258                }
   1259                return Err(e);
   1260            }
   1261        };
   1262        Ok(MarionetteConnection {
   1263            browser,
   1264            session,
   1265            stream,
   1266        })
   1267    }
   1268 
   1269    fn connect(host: &str, browser: &mut Browser) -> WebDriverResult<TcpStream> {
   1270        let timeout = time::Duration::from_secs(60);
   1271        let poll_interval = time::Duration::from_millis(100);
   1272        let now = time::Instant::now();
   1273 
   1274        debug!(
   1275            "Waiting {}s to connect to browser on {}",
   1276            timeout.as_secs(),
   1277            host,
   1278        );
   1279 
   1280        loop {
   1281            // immediately abort connection attempts if process disappears
   1282            if let Browser::Local(browser) = browser {
   1283                if let Some(status) = browser.check_status() {
   1284                    return Err(WebDriverError::new(
   1285                        ErrorStatus::UnknownError,
   1286                        format!("Process unexpectedly closed with status {}", status),
   1287                    ));
   1288                }
   1289            }
   1290 
   1291            let last_err;
   1292 
   1293            if let Some(port) = browser.marionette_port()? {
   1294                match MarionetteConnection::try_connect(host, port) {
   1295                    Ok(stream) => {
   1296                        debug!("Connection to Marionette established on {}:{}.", host, port);
   1297                        browser.update_marionette_port(port);
   1298                        return Ok(stream);
   1299                    }
   1300                    Err(e) => {
   1301                        let err_str = e.to_string();
   1302                        last_err = Some(err_str);
   1303                    }
   1304                }
   1305            } else {
   1306                last_err = Some("Failed to read marionette port".into());
   1307            }
   1308            if now.elapsed() < timeout {
   1309                trace!("Retrying in {:?}", poll_interval);
   1310                thread::sleep(poll_interval);
   1311            } else {
   1312                return Err(WebDriverError::new(
   1313                    ErrorStatus::Timeout,
   1314                    last_err.unwrap_or_else(|| "Unknown error".into()),
   1315                ));
   1316            }
   1317        }
   1318    }
   1319 
   1320    fn try_connect(host: &str, port: u16) -> WebDriverResult<TcpStream> {
   1321        let mut stream = TcpStream::connect((host, port))?;
   1322        MarionetteConnection::handshake(&mut stream)?;
   1323        Ok(stream)
   1324    }
   1325 
   1326    fn handshake(stream: &mut TcpStream) -> WebDriverResult<MarionetteHandshake> {
   1327        let resp = (match stream.read_timeout() {
   1328            Ok(timeout) => {
   1329                // If platform supports changing the read timeout of the stream,
   1330                // use a short one only for the handshake with Marionette. Don't
   1331                // make it shorter as 1000ms to not fail on slow connections.
   1332                stream
   1333                    .set_read_timeout(Some(time::Duration::from_millis(1000)))
   1334                    .ok();
   1335                let data = MarionetteConnection::read_resp(stream);
   1336                stream.set_read_timeout(timeout).ok();
   1337 
   1338                data
   1339            }
   1340            _ => MarionetteConnection::read_resp(stream),
   1341        })
   1342        .map_err(|e| {
   1343            WebDriverError::new(
   1344                ErrorStatus::UnknownError,
   1345                format!("Socket timeout reading Marionette handshake data: {}", e),
   1346            )
   1347        })?;
   1348 
   1349        let data = serde_json::from_str::<MarionetteHandshake>(&resp)?;
   1350 
   1351        if data.application_type != "gecko" {
   1352            return Err(WebDriverError::new(
   1353                ErrorStatus::UnknownError,
   1354                format!("Unrecognized application type {}", data.application_type),
   1355            ));
   1356        }
   1357 
   1358        if data.protocol != 3 {
   1359            return Err(WebDriverError::new(
   1360                ErrorStatus::UnknownError,
   1361                format!(
   1362                    "Unsupported Marionette protocol version {}, required 3",
   1363                    data.protocol
   1364                ),
   1365            ));
   1366        }
   1367 
   1368        Ok(data)
   1369    }
   1370 
   1371    fn close(self, wait_for_shutdown: bool) -> WebDriverResult<()> {
   1372        // Save minidump files of potential crashes from the profile if requested.
   1373        if let Ok(path) = env::var("MINIDUMP_SAVE_PATH") {
   1374            if let Err(e) = self.save_minidumps(&path) {
   1375                error!(
   1376                    "Failed to save minidump files to the requested location: {}",
   1377                    e
   1378                );
   1379            }
   1380        } else {
   1381            debug!("To store minidump files of Firefox crashes the MINIDUMP_SAVE_PATH environment variable needs to be set.");
   1382        }
   1383 
   1384        self.stream.shutdown(Shutdown::Both)?;
   1385        self.browser.close(wait_for_shutdown)?;
   1386        Ok(())
   1387    }
   1388 
   1389    fn save_minidumps(&self, save_path: &str) -> WebDriverResult<()> {
   1390        if !PathBuf::from(&save_path).is_dir() {
   1391            if let Err(e) = fs::create_dir(save_path) {
   1392                warn!(
   1393                    "The specified folder '{}' for minidumps doesn't exist and creation failed: {}",
   1394                    save_path, e
   1395                );
   1396 
   1397                return Ok(());
   1398            }
   1399        }
   1400 
   1401        match &self.browser {
   1402            Browser::Local(browser) => {
   1403                if let Some(profile_path) = &browser.profile_path {
   1404                    copy_minidumps_files(profile_path.as_path(), Path::new(&save_path))?;
   1405                }
   1406            }
   1407            Browser::Remote(browser) => {
   1408                browser.handler.copy_minidumps_files(save_path)?;
   1409            }
   1410            Browser::Existing(_) => return Ok(()),
   1411        }
   1412 
   1413        Ok(())
   1414    }
   1415 
   1416    fn send_command(
   1417        &mut self,
   1418        msg: &WebDriverMessage<GeckoExtensionRoute>,
   1419    ) -> WebDriverResult<WebDriverResponse> {
   1420        let id = self.session.next_command_id();
   1421        let enc_cmd = MarionetteCommand::from_webdriver_message(
   1422            id,
   1423            &self.session.capabilities,
   1424            &self.browser,
   1425            msg,
   1426        )?;
   1427        let resp_data = self.send(enc_cmd)?;
   1428        let data: MarionetteResponse = serde_json::from_str(&resp_data)?;
   1429 
   1430        self.session.response(msg, data)
   1431    }
   1432 
   1433    fn send(&mut self, data: String) -> WebDriverResult<String> {
   1434        if self.stream.write(data.as_bytes()).is_err() {
   1435            let mut err = WebDriverError::new(
   1436                ErrorStatus::UnknownError,
   1437                "Failed to write request to stream",
   1438            );
   1439            err.delete_session = true;
   1440            return Err(err);
   1441        }
   1442 
   1443        match MarionetteConnection::read_resp(&mut self.stream) {
   1444            Ok(resp) => Ok(resp),
   1445            Err(_) => {
   1446                let mut err = WebDriverError::new(
   1447                    ErrorStatus::UnknownError,
   1448                    "Failed to decode response from marionette",
   1449                );
   1450                err.delete_session = true;
   1451                Err(err)
   1452            }
   1453        }
   1454    }
   1455 
   1456    fn read_resp(stream: &mut TcpStream) -> IoResult<String> {
   1457        let mut bytes = 0usize;
   1458 
   1459        loop {
   1460            let buf = &mut [0u8];
   1461            let num_read = stream.read(buf)?;
   1462            let byte = match num_read {
   1463                0 => {
   1464                    return Err(IoError::new(
   1465                        ErrorKind::Other,
   1466                        "EOF reading marionette message",
   1467                    ))
   1468                }
   1469                1 => buf[0],
   1470                _ => panic!("Expected one byte got more"),
   1471            } as char;
   1472            match byte {
   1473                '0'..='9' => {
   1474                    bytes *= 10;
   1475                    bytes += byte as usize - '0' as usize;
   1476                }
   1477                ':' => break,
   1478                _ => {}
   1479            }
   1480        }
   1481 
   1482        let buf = &mut [0u8; 8192];
   1483        let mut payload = Vec::with_capacity(bytes);
   1484        let mut total_read = 0;
   1485        while total_read < bytes {
   1486            let num_read = stream.read(buf)?;
   1487            if num_read == 0 {
   1488                return Err(IoError::new(
   1489                    ErrorKind::Other,
   1490                    "EOF reading marionette message",
   1491                ));
   1492            }
   1493            total_read += num_read;
   1494            for x in &buf[..num_read] {
   1495                payload.push(*x);
   1496            }
   1497        }
   1498 
   1499        // TODO(jgraham): Need to handle the error here
   1500        Ok(String::from_utf8(payload).unwrap())
   1501    }
   1502 }
   1503 
   1504 fn copy_minidumps_files(profile_path: &Path, save_path: &Path) -> WebDriverResult<()> {
   1505    let mut minidumps_path = profile_path.to_path_buf();
   1506    minidumps_path.push("minidumps");
   1507 
   1508    // Check if the folder exists and not empty.
   1509    if !minidumps_path.exists() || minidumps_path.read_dir()?.next().is_none() {
   1510        return Ok(());
   1511    }
   1512 
   1513    match std::fs::read_dir(&minidumps_path) {
   1514        Ok(entries) => {
   1515            for result_entry in entries {
   1516                let entry = result_entry?;
   1517                let file_type = entry.file_type()?;
   1518 
   1519                if file_type.is_dir() {
   1520                    continue;
   1521                }
   1522 
   1523                let path = entry.path();
   1524                let extension = path
   1525                    .extension()
   1526                    .and_then(|ext| ext.to_str())
   1527                    .map(|ext| ext.to_lowercase())
   1528                    .unwrap_or(String::from(""));
   1529 
   1530                // Copy only *.dmp and *.extra files.
   1531                if extension == "dmp" || extension == "extra" {
   1532                    let dest_path = save_path.join(entry.file_name());
   1533                    fs::copy(path, &dest_path)?;
   1534 
   1535                    debug!(
   1536                        "Copied minidump file {:?} to {:?}.",
   1537                        entry.file_name(),
   1538                        save_path.display()
   1539                    );
   1540                }
   1541            }
   1542        }
   1543        Err(_) => {
   1544            warn!(
   1545                "Couldn't read files from minidumps folder '{}'",
   1546                minidumps_path.display(),
   1547            );
   1548 
   1549            return Ok(());
   1550        }
   1551    }
   1552 
   1553    Ok(())
   1554 }
   1555 
   1556 trait ToMarionette<T> {
   1557    fn to_marionette(&self) -> WebDriverResult<T>;
   1558 }
   1559 
   1560 impl ToMarionette<Map<String, Value>> for AddonPath {
   1561    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
   1562        let mut data = Map::new();
   1563        data.insert("path".to_string(), serde_json::to_value(&self.path)?);
   1564        if self.temporary.is_some() {
   1565            data.insert(
   1566                "temporary".to_string(),
   1567                serde_json::to_value(self.temporary)?,
   1568            );
   1569        }
   1570        if self.allow_private_browsing.is_some() {
   1571            data.insert(
   1572                "allowPrivateBrowsing".to_string(),
   1573                serde_json::to_value(self.allow_private_browsing)?,
   1574            );
   1575        }
   1576        Ok(data)
   1577    }
   1578 }
   1579 
   1580 impl ToMarionette<Map<String, Value>> for AddonUninstallParameters {
   1581    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
   1582        let mut data = Map::new();
   1583        data.insert("id".to_string(), Value::String(self.id.clone()));
   1584        Ok(data)
   1585    }
   1586 }
   1587 
   1588 impl ToMarionette<Map<String, Value>> for GeckoContextParameters {
   1589    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
   1590        let mut data = Map::new();
   1591        data.insert(
   1592            "value".to_owned(),
   1593            serde_json::to_value(self.context.clone())?,
   1594        );
   1595        Ok(data)
   1596    }
   1597 }
   1598 
   1599 impl ToMarionette<MarionettePrintParameters> for PrintParameters {
   1600    fn to_marionette(&self) -> WebDriverResult<MarionettePrintParameters> {
   1601        Ok(MarionettePrintParameters {
   1602            orientation: self.orientation.to_marionette()?,
   1603            scale: self.scale,
   1604            background: self.background,
   1605            page: self.page.to_marionette()?,
   1606            margin: self.margin.to_marionette()?,
   1607            page_ranges: self
   1608                .page_ranges
   1609                .iter()
   1610                .map(|x| x.to_marionette())
   1611                .collect::<WebDriverResult<Vec<_>>>()?,
   1612            shrink_to_fit: self.shrink_to_fit,
   1613        })
   1614    }
   1615 }
   1616 
   1617 impl ToMarionette<MarionettePrintOrientation> for PrintOrientation {
   1618    fn to_marionette(&self) -> WebDriverResult<MarionettePrintOrientation> {
   1619        Ok(match self {
   1620            PrintOrientation::Landscape => MarionettePrintOrientation::Landscape,
   1621            PrintOrientation::Portrait => MarionettePrintOrientation::Portrait,
   1622        })
   1623    }
   1624 }
   1625 
   1626 impl ToMarionette<MarionettePrintPage> for PrintPage {
   1627    fn to_marionette(&self) -> WebDriverResult<MarionettePrintPage> {
   1628        Ok(MarionettePrintPage {
   1629            width: self.width,
   1630            height: self.height,
   1631        })
   1632    }
   1633 }
   1634 
   1635 impl ToMarionette<MarionettePrintPageRange> for PrintPageRange {
   1636    fn to_marionette(&self) -> WebDriverResult<MarionettePrintPageRange> {
   1637        Ok(match self {
   1638            PrintPageRange::Integer(num) => MarionettePrintPageRange::Integer(*num),
   1639            PrintPageRange::Range(range) => MarionettePrintPageRange::Range(range.clone()),
   1640        })
   1641    }
   1642 }
   1643 
   1644 impl ToMarionette<MarionettePrintMargins> for PrintMargins {
   1645    fn to_marionette(&self) -> WebDriverResult<MarionettePrintMargins> {
   1646        Ok(MarionettePrintMargins {
   1647            top: self.top,
   1648            bottom: self.bottom,
   1649            left: self.left,
   1650            right: self.right,
   1651        })
   1652    }
   1653 }
   1654 
   1655 impl ToMarionette<MarionetteSetPermissionParameters> for SetPermissionParameters {
   1656    fn to_marionette(&self) -> WebDriverResult<MarionetteSetPermissionParameters> {
   1657        Ok(MarionetteSetPermissionParameters {
   1658            descriptor: self.descriptor.to_marionette()?,
   1659            state: self.state.to_marionette()?,
   1660        })
   1661    }
   1662 }
   1663 
   1664 impl ToMarionette<MarionetteSetPermissionDescriptor> for SetPermissionDescriptor {
   1665    fn to_marionette(&self) -> WebDriverResult<MarionetteSetPermissionDescriptor> {
   1666        Ok(MarionetteSetPermissionDescriptor {
   1667            name: self.name.clone(),
   1668        })
   1669    }
   1670 }
   1671 
   1672 impl ToMarionette<MarionetteSetPermissionState> for SetPermissionState {
   1673    fn to_marionette(&self) -> WebDriverResult<MarionetteSetPermissionState> {
   1674        Ok(match self {
   1675            SetPermissionState::Denied => MarionetteSetPermissionState::Denied,
   1676            SetPermissionState::Granted => MarionetteSetPermissionState::Granted,
   1677            SetPermissionState::Prompt => MarionetteSetPermissionState::Prompt,
   1678        })
   1679    }
   1680 }
   1681 
   1682 impl ToMarionette<MarionetteAuthenticatorParameters> for AuthenticatorParameters {
   1683    fn to_marionette(&self) -> WebDriverResult<MarionetteAuthenticatorParameters> {
   1684        Ok(MarionetteAuthenticatorParameters {
   1685            protocol: self.protocol.to_marionette()?,
   1686            transport: self.transport.to_marionette()?,
   1687            has_resident_key: self.has_resident_key,
   1688            has_user_verification: self.has_user_verification,
   1689            is_user_consenting: self.is_user_consenting,
   1690            is_user_verified: self.is_user_verified,
   1691        })
   1692    }
   1693 }
   1694 
   1695 impl ToMarionette<MarionetteAuthenticatorTransport> for AuthenticatorTransport {
   1696    fn to_marionette(&self) -> WebDriverResult<MarionetteAuthenticatorTransport> {
   1697        Ok(match self {
   1698            AuthenticatorTransport::Usb => MarionetteAuthenticatorTransport::Usb,
   1699            AuthenticatorTransport::Nfc => MarionetteAuthenticatorTransport::Nfc,
   1700            AuthenticatorTransport::Ble => MarionetteAuthenticatorTransport::Ble,
   1701            AuthenticatorTransport::SmartCard => MarionetteAuthenticatorTransport::SmartCard,
   1702            AuthenticatorTransport::Hybrid => MarionetteAuthenticatorTransport::Hybrid,
   1703            AuthenticatorTransport::Internal => MarionetteAuthenticatorTransport::Internal,
   1704        })
   1705    }
   1706 }
   1707 
   1708 impl ToMarionette<MarionetteCredentialParameters> for CredentialParameters {
   1709    fn to_marionette(&self) -> WebDriverResult<MarionetteCredentialParameters> {
   1710        Ok(MarionetteCredentialParameters {
   1711            credential_id: self.credential_id.clone(),
   1712            is_resident_credential: self.is_resident_credential,
   1713            rp_id: self.rp_id.clone(),
   1714            private_key: self.private_key.clone(),
   1715            user_handle: self.user_handle.clone(),
   1716            sign_count: self.sign_count,
   1717        })
   1718    }
   1719 }
   1720 
   1721 impl ToMarionette<MarionetteUserVerificationParameters> for UserVerificationParameters {
   1722    fn to_marionette(&self) -> WebDriverResult<MarionetteUserVerificationParameters> {
   1723        Ok(MarionetteUserVerificationParameters {
   1724            is_user_verified: self.is_user_verified,
   1725        })
   1726    }
   1727 }
   1728 
   1729 impl ToMarionette<MarionetteWebAuthnProtocol> for WebAuthnProtocol {
   1730    fn to_marionette(&self) -> WebDriverResult<MarionetteWebAuthnProtocol> {
   1731        Ok(match self {
   1732            WebAuthnProtocol::Ctap1U2f => MarionetteWebAuthnProtocol::Ctap1U2f,
   1733            WebAuthnProtocol::Ctap2 => MarionetteWebAuthnProtocol::Ctap2,
   1734            WebAuthnProtocol::Ctap2_1 => MarionetteWebAuthnProtocol::Ctap2_1,
   1735        })
   1736    }
   1737 }
   1738 
   1739 impl ToMarionette<Map<String, Value>> for ActionsParameters {
   1740    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
   1741        Ok(try_opt!(
   1742            serde_json::to_value(self)?.as_object(),
   1743            ErrorStatus::UnknownError,
   1744            "Expected an object"
   1745        )
   1746        .clone())
   1747    }
   1748 }
   1749 
   1750 impl ToMarionette<MarionetteCookie> for AddCookieParameters {
   1751    fn to_marionette(&self) -> WebDriverResult<MarionetteCookie> {
   1752        Ok(MarionetteCookie {
   1753            name: self.name.clone(),
   1754            value: self.value.clone(),
   1755            path: self.path.clone(),
   1756            domain: self.domain.clone(),
   1757            secure: self.secure,
   1758            http_only: self.httpOnly,
   1759            expiry: match &self.expiry {
   1760                Some(date) => Some(date.to_marionette()?),
   1761                None => None,
   1762            },
   1763            same_site: self.sameSite.clone(),
   1764        })
   1765    }
   1766 }
   1767 
   1768 impl ToMarionette<MarionetteDate> for Date {
   1769    fn to_marionette(&self) -> WebDriverResult<MarionetteDate> {
   1770        Ok(MarionetteDate(self.0))
   1771    }
   1772 }
   1773 
   1774 impl ToMarionette<Map<String, Value>> for GetNamedCookieParameters {
   1775    fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
   1776        Ok(try_opt!(
   1777            serde_json::to_value(self)?.as_object(),
   1778            ErrorStatus::UnknownError,
   1779            "Expected an object"
   1780        )
   1781        .clone())
   1782    }
   1783 }
   1784 
   1785 impl ToMarionette<MarionetteUrl> for GetParameters {
   1786    fn to_marionette(&self) -> WebDriverResult<MarionetteUrl> {
   1787        Ok(MarionetteUrl {
   1788            url: self.url.clone(),
   1789        })
   1790    }
   1791 }
   1792 
   1793 impl ToMarionette<MarionetteScript> for JavascriptCommandParameters {
   1794    fn to_marionette(&self) -> WebDriverResult<MarionetteScript> {
   1795        Ok(MarionetteScript {
   1796            script: self.script.clone(),
   1797            args: self.args.clone(),
   1798        })
   1799    }
   1800 }
   1801 
   1802 impl ToMarionette<MarionetteLocator> for LocatorParameters {
   1803    fn to_marionette(&self) -> WebDriverResult<MarionetteLocator> {
   1804        Ok(MarionetteLocator {
   1805            element: None,
   1806            using: self.using.to_marionette()?,
   1807            value: self.value.clone(),
   1808        })
   1809    }
   1810 }
   1811 
   1812 impl ToMarionette<MarionetteSelector> for LocatorStrategy {
   1813    fn to_marionette(&self) -> WebDriverResult<MarionetteSelector> {
   1814        use self::LocatorStrategy::*;
   1815        match self {
   1816            CSSSelector => Ok(MarionetteSelector::Css),
   1817            LinkText => Ok(MarionetteSelector::LinkText),
   1818            PartialLinkText => Ok(MarionetteSelector::PartialLinkText),
   1819            TagName => Ok(MarionetteSelector::TagName),
   1820            XPath => Ok(MarionetteSelector::XPath),
   1821        }
   1822    }
   1823 }
   1824 
   1825 impl ToMarionette<MarionetteNewWindow> for NewWindowParameters {
   1826    fn to_marionette(&self) -> WebDriverResult<MarionetteNewWindow> {
   1827        Ok(MarionetteNewWindow {
   1828            type_hint: self.type_hint.clone(),
   1829        })
   1830    }
   1831 }
   1832 
   1833 impl ToMarionette<MarionetteKeys> for SendKeysParameters {
   1834    fn to_marionette(&self) -> WebDriverResult<MarionetteKeys> {
   1835        Ok(MarionetteKeys {
   1836            text: self.text.clone(),
   1837            value: self
   1838                .text
   1839                .chars()
   1840                .map(|x| x.to_string())
   1841                .collect::<Vec<String>>(),
   1842        })
   1843    }
   1844 }
   1845 
   1846 impl ToMarionette<MarionetteFrame> for SwitchToFrameParameters {
   1847    fn to_marionette(&self) -> WebDriverResult<MarionetteFrame> {
   1848        Ok(match &self.id {
   1849            FrameId::Short(n) => MarionetteFrame::Index(*n),
   1850            FrameId::Element(el) => MarionetteFrame::Element(el.0.clone()),
   1851            FrameId::Top => MarionetteFrame::Top,
   1852        })
   1853    }
   1854 }
   1855 
   1856 impl ToMarionette<Window> for SwitchToWindowParameters {
   1857    fn to_marionette(&self) -> WebDriverResult<Window> {
   1858        Ok(Window {
   1859            handle: self.handle.clone(),
   1860        })
   1861    }
   1862 }
   1863 
   1864 impl ToMarionette<MarionetteTimeouts> for TimeoutsParameters {
   1865    fn to_marionette(&self) -> WebDriverResult<MarionetteTimeouts> {
   1866        Ok(MarionetteTimeouts {
   1867            implicit: self.implicit,
   1868            page_load: self.page_load,
   1869            script: self.script,
   1870        })
   1871    }
   1872 }
   1873 
   1874 impl ToMarionette<MarionetteWebElement> for WebElement {
   1875    fn to_marionette(&self) -> WebDriverResult<MarionetteWebElement> {
   1876        Ok(MarionetteWebElement {
   1877            element: self.to_string(),
   1878        })
   1879    }
   1880 }
   1881 
   1882 impl ToMarionette<MarionetteWindowRect> for WindowRectParameters {
   1883    fn to_marionette(&self) -> WebDriverResult<MarionetteWindowRect> {
   1884        Ok(MarionetteWindowRect {
   1885            x: self.x,
   1886            y: self.y,
   1887            width: self.width,
   1888            height: self.height,
   1889        })
   1890    }
   1891 }
   1892 
   1893 impl ToMarionette<MarionetteGlobalPrivacyControlParameters> for GlobalPrivacyControlParameters {
   1894    fn to_marionette(&self) -> WebDriverResult<MarionetteGlobalPrivacyControlParameters> {
   1895        Ok(MarionetteGlobalPrivacyControlParameters { gpc: self.gpc })
   1896    }
   1897 }
   1898 
   1899 #[cfg(test)]
   1900 mod tests {
   1901    use super::*;
   1902    use std::fs::File;
   1903    use std::path::Path;
   1904    use tempfile::TempDir;
   1905 
   1906    fn assert_minidump_files(minidumps_path: &Path, filename: &str) {
   1907        let mut dmp_file_present = false;
   1908        let mut extra_file_present = false;
   1909 
   1910        for result_entry in std::fs::read_dir(minidumps_path).unwrap() {
   1911            let entry = result_entry.unwrap();
   1912 
   1913            let path: PathBuf = entry.path();
   1914            let filename_from_path = path.file_stem().unwrap().to_str().unwrap();
   1915            if filename == filename_from_path {
   1916                let extension = path.extension().and_then(|ext| ext.to_str()).unwrap();
   1917 
   1918                if extension == "dmp" {
   1919                    dmp_file_present = true;
   1920                }
   1921 
   1922                if extension == "extra" {
   1923                    extra_file_present = true;
   1924                }
   1925            }
   1926        }
   1927 
   1928        assert!(dmp_file_present);
   1929        assert!(extra_file_present);
   1930    }
   1931 
   1932    fn create_file(folder: &Path, filename: &str) {
   1933        let file = folder.join(filename);
   1934        File::create(&file).unwrap();
   1935    }
   1936 
   1937    fn create_minidump_files(profile_path: &Path, filename: &str) {
   1938        let folder = create_minidump_folder(profile_path);
   1939 
   1940        let mut file_extensions = [".dmp", ".extra"];
   1941        for file_extension in file_extensions.iter_mut() {
   1942            let mut filename_with_extension: String = filename.to_owned();
   1943            filename_with_extension.push_str(file_extension);
   1944 
   1945            create_file(&folder, &filename_with_extension);
   1946        }
   1947    }
   1948 
   1949    fn create_minidump_folder(profile_path: &Path) -> PathBuf {
   1950        let minidumps_folder = profile_path.join("minidumps");
   1951        if !minidumps_folder.is_dir() {
   1952            fs::create_dir(&minidumps_folder).unwrap();
   1953        }
   1954 
   1955        minidumps_folder
   1956    }
   1957 
   1958    #[test]
   1959    fn test_copy_minidumps() {
   1960        let tmp_dir_profile = TempDir::new().unwrap();
   1961        let profile_path = tmp_dir_profile.path();
   1962 
   1963        let filename = "test";
   1964 
   1965        create_minidump_files(profile_path, filename);
   1966 
   1967        let tmp_dir_minidumps = TempDir::new().unwrap();
   1968        let minidumps_path = tmp_dir_minidumps.path();
   1969 
   1970        copy_minidumps_files(profile_path, minidumps_path).unwrap();
   1971 
   1972        assert_minidump_files(minidumps_path, filename);
   1973 
   1974        tmp_dir_profile.close().unwrap();
   1975        tmp_dir_minidumps.close().unwrap();
   1976    }
   1977 
   1978    #[test]
   1979    fn test_copy_multiple_minidumps() {
   1980        let tmp_dir_profile = TempDir::new().unwrap();
   1981        let profile_path = tmp_dir_profile.path();
   1982 
   1983        let filename_1 = "test_1";
   1984        create_minidump_files(profile_path, filename_1);
   1985 
   1986        let filename_2 = "test_2";
   1987        create_minidump_files(profile_path, filename_2);
   1988 
   1989        let tmp_dir_minidumps = TempDir::new().unwrap();
   1990        let minidumps_path = tmp_dir_minidumps.path();
   1991 
   1992        copy_minidumps_files(profile_path, minidumps_path).unwrap();
   1993 
   1994        assert_minidump_files(minidumps_path, filename_1);
   1995        assert_minidump_files(minidumps_path, filename_1);
   1996 
   1997        tmp_dir_profile.close().unwrap();
   1998        tmp_dir_minidumps.close().unwrap();
   1999    }
   2000 
   2001    #[test]
   2002    fn test_copy_minidumps_with_non_existent_manifest_path() {
   2003        let tmp_dir_profile = TempDir::new().unwrap();
   2004        let profile_path = tmp_dir_profile.path();
   2005 
   2006        create_minidump_folder(profile_path);
   2007 
   2008        assert!(copy_minidumps_files(profile_path, Path::new("/non-existent")).is_ok());
   2009 
   2010        tmp_dir_profile.close().unwrap();
   2011    }
   2012 
   2013    #[test]
   2014    fn test_copy_minidumps_with_non_existent_profile_path() {
   2015        let tmp_dir_minidumps = TempDir::new().unwrap();
   2016        let minidumps_path = tmp_dir_minidumps.path();
   2017 
   2018        assert!(copy_minidumps_files(Path::new("/non-existent"), minidumps_path).is_ok());
   2019 
   2020        tmp_dir_minidumps.close().unwrap();
   2021    }
   2022 
   2023    #[test]
   2024    fn test_copy_minidumps_with_non_minidumps_files() {
   2025        let tmp_dir_profile = TempDir::new().unwrap();
   2026        let profile_path = tmp_dir_profile.path();
   2027 
   2028        let minidumps_folder = create_minidump_folder(profile_path);
   2029 
   2030        // Create a folder.
   2031        let test_folder_binding = profile_path.join("test");
   2032        let test_folder = test_folder_binding.as_path();
   2033        fs::create_dir(test_folder).unwrap();
   2034 
   2035        // Create a file with non minidumps extension.
   2036        create_file(&minidumps_folder, "test.txt");
   2037 
   2038        let tmp_dir_minidumps = TempDir::new().unwrap();
   2039        let minidumps_path = tmp_dir_minidumps.path();
   2040 
   2041        copy_minidumps_files(profile_path, minidumps_path).unwrap();
   2042 
   2043        // Check that the non minidump file and the folder were not copied.
   2044        assert!(minidumps_path.read_dir().unwrap().next().is_none());
   2045 
   2046        tmp_dir_profile.close().unwrap();
   2047        tmp_dir_minidumps.close().unwrap();
   2048    }
   2049 }