Bug 1824751 [wpt PR 39215] - [FLEDGE] Add "use strict" to FLEDGE WPT tests' JS script...
[gecko.git] / testing / geckodriver / src / marionette.rs
blob836382b7dbf825a228b1fc47143e6384631f9b22
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/. */
5 use crate::browser::{Browser, LocalBrowser, RemoteBrowser};
6 use crate::build;
7 use crate::capabilities::{FirefoxCapabilities, FirefoxOptions, ProfileType};
8 use crate::command::{
9     AddonInstallParameters, AddonUninstallParameters, GeckoContextParameters,
10     GeckoExtensionCommand, GeckoExtensionRoute,
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,
17 use marionette_rs::marionette::AppStatus;
18 use marionette_rs::message::{Command, Message, MessageId, Request};
19 use marionette_rs::webdriver::{
20     Command as MarionetteWebDriverCommand, Keys as MarionetteKeys, Locator as MarionetteLocator,
21     NewWindow as MarionetteNewWindow, PrintMargins as MarionettePrintMargins,
22     PrintOrientation as MarionettePrintOrientation, PrintPage as MarionettePrintPage,
23     PrintParameters as MarionettePrintParameters, ScreenshotOptions, Script as MarionetteScript,
24     Selector as MarionetteSelector, Url as MarionetteUrl, WindowRect as MarionetteWindowRect,
26 use mozdevice::AndroidStorageInput;
27 use serde::de::{self, Deserialize, Deserializer};
28 use serde::ser::{Serialize, Serializer};
29 use serde_json::{self, Map, Value};
30 use std::io::prelude::*;
31 use std::io::Error as IoError;
32 use std::io::ErrorKind;
33 use std::io::Result as IoResult;
34 use std::net::{Shutdown, TcpListener, TcpStream};
35 use std::path::PathBuf;
36 use std::sync::Mutex;
37 use std::thread;
38 use std::time;
39 use url::{Host, Url};
40 use webdriver::capabilities::BrowserCapabilities;
41 use webdriver::command::WebDriverCommand::{
42     AcceptAlert, AddCookie, CloseWindow, DeleteCookie, DeleteCookies, DeleteSession, DismissAlert,
43     ElementClear, ElementClick, ElementSendKeys, ExecuteAsyncScript, ExecuteScript, Extension,
44     FindElement, FindElementElement, FindElementElements, FindElements, FindShadowRootElement,
45     FindShadowRootElements, FullscreenWindow, Get, GetActiveElement, GetAlertText, GetCSSValue,
46     GetComputedLabel, GetComputedRole, GetCookies, GetCurrentUrl, GetElementAttribute,
47     GetElementProperty, GetElementRect, GetElementTagName, GetElementText, GetNamedCookie,
48     GetPageSource, GetShadowRoot, GetTimeouts, GetTitle, GetWindowHandle, GetWindowHandles,
49     GetWindowRect, GoBack, GoForward, IsDisplayed, IsEnabled, IsSelected, MaximizeWindow,
50     MinimizeWindow, NewSession, NewWindow, PerformActions, Print, Refresh, ReleaseActions,
51     SendAlertText, SetTimeouts, SetWindowRect, Status, SwitchToFrame, SwitchToParentFrame,
52     SwitchToWindow, TakeElementScreenshot, TakeScreenshot,
54 use webdriver::command::{
55     ActionsParameters, AddCookieParameters, GetNamedCookieParameters, GetParameters,
56     JavascriptCommandParameters, LocatorParameters, NewSessionParameters, NewWindowParameters,
57     PrintMargins, PrintOrientation, PrintPage, PrintParameters, SendKeysParameters,
58     SwitchToFrameParameters, SwitchToWindowParameters, TimeoutsParameters, WindowRectParameters,
60 use webdriver::command::{WebDriverCommand, WebDriverMessage};
61 use webdriver::common::{
62     Cookie, Date, FrameId, LocatorStrategy, ShadowRoot, WebElement, ELEMENT_KEY, FRAME_KEY,
63     SHADOW_KEY, WINDOW_KEY,
65 use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult};
66 use webdriver::response::{
67     CloseWindowResponse, CookieResponse, CookiesResponse, ElementRectResponse, NewSessionResponse,
68     NewWindowResponse, TimeoutsResponse, ValueResponse, WebDriverResponse, WindowRectResponse,
70 use webdriver::server::{Session, WebDriverHandler};
71 use webdriver::{capabilities::CapabilitiesMatching, server::SessionTeardownKind};
73 #[derive(Debug, PartialEq, Deserialize)]
74 struct MarionetteHandshake {
75     #[serde(rename = "marionetteProtocol")]
76     protocol: u16,
77     #[serde(rename = "applicationType")]
78     application_type: String,
81 #[derive(Default)]
82 pub(crate) struct MarionetteSettings {
83     pub(crate) binary: Option<PathBuf>,
84     pub(crate) profile_root: Option<PathBuf>,
85     pub(crate) connect_existing: bool,
86     pub(crate) host: String,
87     pub(crate) port: Option<u16>,
88     pub(crate) websocket_port: u16,
89     pub(crate) allow_hosts: Vec<Host>,
90     pub(crate) allow_origins: Vec<Url>,
92     /// Brings up the Browser Toolbox when starting Firefox,
93     /// letting you debug internals.
94     pub(crate) jsdebugger: bool,
96     pub(crate) android_storage: AndroidStorageInput,
99 #[derive(Default)]
100 pub(crate) struct MarionetteHandler {
101     connection: Mutex<Option<MarionetteConnection>>,
102     settings: MarionetteSettings,
105 impl MarionetteHandler {
106     pub(crate) fn new(settings: MarionetteSettings) -> MarionetteHandler {
107         MarionetteHandler {
108             connection: Mutex::new(None),
109             settings,
110         }
111     }
113     fn create_connection(
114         &self,
115         session_id: Option<String>,
116         new_session_parameters: &NewSessionParameters,
117     ) -> WebDriverResult<MarionetteConnection> {
118         let mut fx_capabilities = FirefoxCapabilities::new(self.settings.binary.as_ref());
119         let (capabilities, options) = {
120             let mut capabilities = new_session_parameters
121                 .match_browser(&mut fx_capabilities)?
122                 .ok_or_else(|| {
123                     WebDriverError::new(
124                         ErrorStatus::SessionNotCreated,
125                         "Unable to find a matching set of capabilities",
126                     )
127                 })?;
129             let options = FirefoxOptions::from_capabilities(
130                 fx_capabilities.chosen_binary.clone(),
131                 &self.settings,
132                 &mut capabilities,
133             )?;
134             (capabilities, options)
135         };
137         if let Some(l) = options.log.level {
138             logging::set_max_level(l);
139         }
141         let marionette_host = self.settings.host.to_owned();
142         let marionette_port = match self.settings.port {
143             Some(port) => port,
144             None => {
145                 // If we're launching Firefox Desktop version 95 or later, and there's no port
146                 // specified, we can pass 0 as the port and later read it back from
147                 // the profile.
148                 let can_use_profile: bool = options.android.is_none()
149                     && options.profile != ProfileType::Named
150                     && !self.settings.connect_existing
151                     && fx_capabilities
152                         .browser_version(&capabilities)
153                         .map(|opt_v| {
154                             opt_v
155                                 .map(|v| {
156                                     fx_capabilities
157                                         .compare_browser_version(&v, ">=95")
158                                         .unwrap_or(false)
159                                 })
160                                 .unwrap_or(false)
161                         })
162                         .unwrap_or(false);
163                 if can_use_profile {
164                     0
165                 } else {
166                     get_free_port(&marionette_host)?
167                 }
168             }
169         };
171         let websocket_port = if options.use_websocket {
172             Some(self.settings.websocket_port)
173         } else {
174             None
175         };
177         let browser = if options.android.is_some() {
178             // TODO: support connecting to running Apps.  There's no real obstruction here,
179             // just some details about port forwarding to work through.  We can't follow
180             // `chromedriver` here since it uses an abstract socket rather than a TCP socket:
181             // see bug 1240830 for thoughts on doing that for Marionette.
182             if self.settings.connect_existing {
183                 return Err(WebDriverError::new(
184                     ErrorStatus::SessionNotCreated,
185                     "Cannot connect to an existing Android App yet",
186                 ));
187             }
188             Browser::Remote(RemoteBrowser::new(
189                 options,
190                 marionette_port,
191                 websocket_port,
192                 self.settings.profile_root.as_deref(),
193             )?)
194         } else if !self.settings.connect_existing {
195             Browser::Local(LocalBrowser::new(
196                 options,
197                 marionette_port,
198                 self.settings.jsdebugger,
199                 self.settings.profile_root.as_deref(),
200             )?)
201         } else {
202             Browser::Existing(marionette_port)
203         };
204         let session = MarionetteSession::new(session_id, capabilities);
205         MarionetteConnection::new(marionette_host, browser, session)
206     }
208     fn close_connection(&mut self, wait_for_shutdown: bool) {
209         if let Ok(connection) = self.connection.get_mut() {
210             if let Some(conn) = connection.take() {
211                 if let Err(e) = conn.close(wait_for_shutdown) {
212                     error!("Failed to close browser connection: {}", e)
213                 }
214             }
215         }
216     }
219 impl WebDriverHandler<GeckoExtensionRoute> for MarionetteHandler {
220     fn handle_command(
221         &mut self,
222         _: &Option<Session>,
223         msg: WebDriverMessage<GeckoExtensionRoute>,
224     ) -> WebDriverResult<WebDriverResponse> {
225         // First handle the status message which doesn't actually require a marionette
226         // connection or message
227         if let Status = msg.command {
228             let (ready, message) = self
229                 .connection
230                 .get_mut()
231                 .map(|ref connection| {
232                     connection
233                         .as_ref()
234                         .map(|_| (false, "Session already started"))
235                         .unwrap_or((true, ""))
236                 })
237                 .unwrap_or((false, "geckodriver internal error"));
238             let mut value = Map::new();
239             value.insert("ready".to_string(), Value::Bool(ready));
240             value.insert("message".to_string(), Value::String(message.into()));
241             return Ok(WebDriverResponse::Generic(ValueResponse(Value::Object(
242                 value,
243             ))));
244         }
246         match self.connection.lock() {
247             Ok(mut connection) => {
248                 if connection.is_none() {
249                     if let NewSession(ref capabilities) = msg.command {
250                         let conn = self.create_connection(msg.session_id.clone(), capabilities)?;
251                         *connection = Some(conn);
252                     } else {
253                         return Err(WebDriverError::new(
254                             ErrorStatus::InvalidSessionId,
255                             "Tried to run command without establishing a connection",
256                         ));
257                     }
258                 }
259                 let conn = connection.as_mut().expect("Missing connection");
260                 conn.send_command(&msg).map_err(|mut err| {
261                     // Shutdown the browser if no session can
262                     // be established due to errors.
263                     if let NewSession(_) = msg.command {
264                         err.delete_session = true;
265                     }
266                     err
267                 })
268             }
269             Err(_) => Err(WebDriverError::new(
270                 ErrorStatus::UnknownError,
271                 "Failed to aquire Marionette connection",
272             )),
273         }
274     }
276     fn teardown_session(&mut self, kind: SessionTeardownKind) {
277         let wait_for_shutdown = match kind {
278             SessionTeardownKind::Deleted => true,
279             SessionTeardownKind::NotDeleted => false,
280         };
281         self.close_connection(wait_for_shutdown);
282     }
285 impl Drop for MarionetteHandler {
286     fn drop(&mut self) {
287         self.close_connection(false);
288     }
291 struct MarionetteSession {
292     session_id: String,
293     capabilities: Map<String, Value>,
294     command_id: MessageId,
297 impl MarionetteSession {
298     fn new(session_id: Option<String>, capabilities: Map<String, Value>) -> MarionetteSession {
299         let initital_id = session_id.unwrap_or_default();
300         MarionetteSession {
301             session_id: initital_id,
302             capabilities,
303             command_id: 0,
304         }
305     }
307     fn update(
308         &mut self,
309         msg: &WebDriverMessage<GeckoExtensionRoute>,
310         resp: &MarionetteResponse,
311     ) -> WebDriverResult<()> {
312         if let NewSession(_) = msg.command {
313             let session_id = try_opt!(
314                 try_opt!(
315                     resp.result.get("sessionId"),
316                     ErrorStatus::SessionNotCreated,
317                     "Unable to get session id"
318                 )
319                 .as_str(),
320                 ErrorStatus::SessionNotCreated,
321                 "Unable to convert session id to string"
322             );
323             self.session_id = session_id.to_string();
324         };
325         Ok(())
326     }
328     /// Converts a Marionette JSON response into a `WebElement`.
329     ///
330     /// Note that it currently coerces all chrome elements, web frames, and web
331     /// windows also into web elements.  This will change at a later point.
332     fn to_web_element(&self, json_data: &Value) -> WebDriverResult<WebElement> {
333         let data = try_opt!(
334             json_data.as_object(),
335             ErrorStatus::UnknownError,
336             "Failed to convert data to an object"
337         );
339         let element = data.get(ELEMENT_KEY);
340         let frame = data.get(FRAME_KEY);
341         let window = data.get(WINDOW_KEY);
343         let value = try_opt!(
344             element.or(frame).or(window),
345             ErrorStatus::UnknownError,
346             "Failed to extract web element from Marionette response"
347         );
348         let id = try_opt!(
349             value.as_str(),
350             ErrorStatus::UnknownError,
351             "Failed to convert web element reference value to string"
352         )
353         .to_string();
354         Ok(WebElement(id))
355     }
357     /// Converts a Marionette JSON response into a `ShadowRoot`.
358     fn to_shadow_root(&self, json_data: &Value) -> WebDriverResult<ShadowRoot> {
359         let data = try_opt!(
360             json_data.as_object(),
361             ErrorStatus::UnknownError,
362             "Failed to convert data to an object"
363         );
365         let shadow_root = data.get(SHADOW_KEY);
367         let value = try_opt!(
368             shadow_root,
369             ErrorStatus::UnknownError,
370             "Failed to extract shadow root from Marionette response"
371         );
372         let id = try_opt!(
373             value.as_str(),
374             ErrorStatus::UnknownError,
375             "Failed to convert shadow root reference value to string"
376         )
377         .to_string();
378         Ok(ShadowRoot(id))
379     }
381     fn next_command_id(&mut self) -> MessageId {
382         self.command_id += 1;
383         self.command_id
384     }
386     fn response(
387         &mut self,
388         msg: &WebDriverMessage<GeckoExtensionRoute>,
389         resp: MarionetteResponse,
390     ) -> WebDriverResult<WebDriverResponse> {
391         use self::GeckoExtensionCommand::*;
393         if resp.id != self.command_id {
394             return Err(WebDriverError::new(
395                 ErrorStatus::UnknownError,
396                 format!(
397                     "Marionette responses arrived out of sequence, expected {}, got {}",
398                     self.command_id, resp.id
399                 ),
400             ));
401         }
403         if let Some(error) = resp.error {
404             return Err(error.into());
405         }
407         self.update(msg, &resp)?;
409         Ok(match msg.command {
410             // Everything that doesn't have a response value
411             Get(_)
412             | GoBack
413             | GoForward
414             | Refresh
415             | SetTimeouts(_)
416             | SwitchToWindow(_)
417             | SwitchToFrame(_)
418             | SwitchToParentFrame
419             | AddCookie(_)
420             | DeleteCookies
421             | DeleteCookie(_)
422             | DismissAlert
423             | AcceptAlert
424             | SendAlertText(_)
425             | ElementClick(_)
426             | ElementClear(_)
427             | ElementSendKeys(_, _)
428             | PerformActions(_)
429             | ReleaseActions => WebDriverResponse::Void,
430             // Things that simply return the contents of the marionette "value" property
431             GetCurrentUrl
432             | GetTitle
433             | GetPageSource
434             | GetWindowHandle
435             | IsDisplayed(_)
436             | IsSelected(_)
437             | GetElementAttribute(_, _)
438             | GetElementProperty(_, _)
439             | GetCSSValue(_, _)
440             | GetElementText(_)
441             | GetElementTagName(_)
442             | GetComputedLabel(_)
443             | GetComputedRole(_)
444             | IsEnabled(_)
445             | ExecuteScript(_)
446             | ExecuteAsyncScript(_)
447             | GetAlertText
448             | TakeScreenshot
449             | Print(_)
450             | TakeElementScreenshot(_) => {
451                 WebDriverResponse::Generic(resp.into_value_response(true)?)
452             }
453             GetTimeouts => {
454                 let script = match try_opt!(
455                     resp.result.get("script"),
456                     ErrorStatus::UnknownError,
457                     "Missing field: script"
458                 ) {
459                     Value::Null => None,
460                     n => try_opt!(
461                         Some(n.as_u64()),
462                         ErrorStatus::UnknownError,
463                         "Failed to interpret script timeout duration as u64"
464                     ),
465                 };
466                 let page_load = try_opt!(
467                     try_opt!(
468                         resp.result.get("pageLoad"),
469                         ErrorStatus::UnknownError,
470                         "Missing field: pageLoad"
471                     )
472                     .as_u64(),
473                     ErrorStatus::UnknownError,
474                     "Failed to interpret page load duration as u64"
475                 );
476                 let implicit = try_opt!(
477                     try_opt!(
478                         resp.result.get("implicit"),
479                         ErrorStatus::UnknownError,
480                         "Missing field: implicit"
481                     )
482                     .as_u64(),
483                     ErrorStatus::UnknownError,
484                     "Failed to interpret implicit search duration as u64"
485                 );
487                 WebDriverResponse::Timeouts(TimeoutsResponse {
488                     script,
489                     page_load,
490                     implicit,
491                 })
492             }
493             Status => panic!("Got status command that should already have been handled"),
494             GetWindowHandles => WebDriverResponse::Generic(resp.into_value_response(false)?),
495             NewWindow(_) => {
496                 let handle: String = try_opt!(
497                     try_opt!(
498                         resp.result.get("handle"),
499                         ErrorStatus::UnknownError,
500                         "Failed to find handle field"
501                     )
502                     .as_str(),
503                     ErrorStatus::UnknownError,
504                     "Failed to interpret handle as string"
505                 )
506                 .into();
507                 let typ: String = try_opt!(
508                     try_opt!(
509                         resp.result.get("type"),
510                         ErrorStatus::UnknownError,
511                         "Failed to find type field"
512                     )
513                     .as_str(),
514                     ErrorStatus::UnknownError,
515                     "Failed to interpret type as string"
516                 )
517                 .into();
519                 WebDriverResponse::NewWindow(NewWindowResponse { handle, typ })
520             }
521             CloseWindow => {
522                 let data = try_opt!(
523                     resp.result.as_array(),
524                     ErrorStatus::UnknownError,
525                     "Failed to interpret value as array"
526                 );
527                 let handles = data
528                     .iter()
529                     .map(|x| {
530                         Ok(try_opt!(
531                             x.as_str(),
532                             ErrorStatus::UnknownError,
533                             "Failed to interpret window handle as string"
534                         )
535                         .to_owned())
536                     })
537                     .collect::<Result<Vec<_>, _>>()?;
538                 WebDriverResponse::CloseWindow(CloseWindowResponse(handles))
539             }
540             GetElementRect(_) => {
541                 let x = try_opt!(
542                     try_opt!(
543                         resp.result.get("x"),
544                         ErrorStatus::UnknownError,
545                         "Failed to find x field"
546                     )
547                     .as_f64(),
548                     ErrorStatus::UnknownError,
549                     "Failed to interpret x as float"
550                 );
552                 let y = try_opt!(
553                     try_opt!(
554                         resp.result.get("y"),
555                         ErrorStatus::UnknownError,
556                         "Failed to find y field"
557                     )
558                     .as_f64(),
559                     ErrorStatus::UnknownError,
560                     "Failed to interpret y as float"
561                 );
563                 let width = try_opt!(
564                     try_opt!(
565                         resp.result.get("width"),
566                         ErrorStatus::UnknownError,
567                         "Failed to find width field"
568                     )
569                     .as_f64(),
570                     ErrorStatus::UnknownError,
571                     "Failed to interpret width as float"
572                 );
574                 let height = try_opt!(
575                     try_opt!(
576                         resp.result.get("height"),
577                         ErrorStatus::UnknownError,
578                         "Failed to find height field"
579                     )
580                     .as_f64(),
581                     ErrorStatus::UnknownError,
582                     "Failed to interpret width as float"
583                 );
585                 let rect = ElementRectResponse {
586                     x,
587                     y,
588                     width,
589                     height,
590                 };
591                 WebDriverResponse::ElementRect(rect)
592             }
593             FullscreenWindow | MinimizeWindow | MaximizeWindow | GetWindowRect
594             | SetWindowRect(_) => {
595                 let width = try_opt!(
596                     try_opt!(
597                         resp.result.get("width"),
598                         ErrorStatus::UnknownError,
599                         "Failed to find width field"
600                     )
601                     .as_u64(),
602                     ErrorStatus::UnknownError,
603                     "Failed to interpret width as positive integer"
604                 );
606                 let height = try_opt!(
607                     try_opt!(
608                         resp.result.get("height"),
609                         ErrorStatus::UnknownError,
610                         "Failed to find heigenht field"
611                     )
612                     .as_u64(),
613                     ErrorStatus::UnknownError,
614                     "Failed to interpret height as positive integer"
615                 );
617                 let x = try_opt!(
618                     try_opt!(
619                         resp.result.get("x"),
620                         ErrorStatus::UnknownError,
621                         "Failed to find x field"
622                     )
623                     .as_i64(),
624                     ErrorStatus::UnknownError,
625                     "Failed to interpret x as integer"
626                 );
628                 let y = try_opt!(
629                     try_opt!(
630                         resp.result.get("y"),
631                         ErrorStatus::UnknownError,
632                         "Failed to find y field"
633                     )
634                     .as_i64(),
635                     ErrorStatus::UnknownError,
636                     "Failed to interpret y as integer"
637                 );
639                 let rect = WindowRectResponse {
640                     x: x as i32,
641                     y: y as i32,
642                     width: width as i32,
643                     height: height as i32,
644                 };
645                 WebDriverResponse::WindowRect(rect)
646             }
647             GetCookies => {
648                 let cookies: Vec<Cookie> = serde_json::from_value(resp.result)?;
649                 WebDriverResponse::Cookies(CookiesResponse(cookies))
650             }
651             GetNamedCookie(ref name) => {
652                 let mut cookies: Vec<Cookie> = serde_json::from_value(resp.result)?;
653                 cookies.retain(|x| x.name == *name);
654                 let cookie = try_opt!(
655                     cookies.pop(),
656                     ErrorStatus::NoSuchCookie,
657                     format!("No cookie with name {}", name)
658                 );
659                 WebDriverResponse::Cookie(CookieResponse(cookie))
660             }
661             FindElement(_) | FindElementElement(_, _) | FindShadowRootElement(_, _) => {
662                 let element = self.to_web_element(try_opt!(
663                     resp.result.get("value"),
664                     ErrorStatus::UnknownError,
665                     "Failed to find value field"
666                 ))?;
667                 WebDriverResponse::Generic(ValueResponse(serde_json::to_value(element)?))
668             }
669             FindElements(_) | FindElementElements(_, _) | FindShadowRootElements(_, _) => {
670                 let element_vec = try_opt!(
671                     resp.result.as_array(),
672                     ErrorStatus::UnknownError,
673                     "Failed to interpret value as array"
674                 );
675                 let elements = element_vec
676                     .iter()
677                     .map(|x| self.to_web_element(x))
678                     .collect::<Result<Vec<_>, _>>()?;
680                 // TODO(Henrik): How to remove unwrap?
681                 WebDriverResponse::Generic(ValueResponse(Value::Array(
682                     elements
683                         .iter()
684                         .map(|x| serde_json::to_value(x).unwrap())
685                         .collect(),
686                 )))
687             }
688             GetShadowRoot(_) => {
689                 let shadow_root = self.to_shadow_root(try_opt!(
690                     resp.result.get("value"),
691                     ErrorStatus::UnknownError,
692                     "Failed to find value field"
693                 ))?;
694                 WebDriverResponse::Generic(ValueResponse(serde_json::to_value(shadow_root)?))
695             }
696             GetActiveElement => {
697                 let element = self.to_web_element(try_opt!(
698                     resp.result.get("value"),
699                     ErrorStatus::UnknownError,
700                     "Failed to find value field"
701                 ))?;
702                 WebDriverResponse::Generic(ValueResponse(serde_json::to_value(element)?))
703             }
704             NewSession(_) => {
705                 let session_id = try_opt!(
706                     try_opt!(
707                         resp.result.get("sessionId"),
708                         ErrorStatus::InvalidSessionId,
709                         "Failed to find sessionId field"
710                     )
711                     .as_str(),
712                     ErrorStatus::InvalidSessionId,
713                     "sessionId is not a string"
714                 );
716                 let mut capabilities = try_opt!(
717                     try_opt!(
718                         resp.result.get("capabilities"),
719                         ErrorStatus::UnknownError,
720                         "Failed to find capabilities field"
721                     )
722                     .as_object(),
723                     ErrorStatus::UnknownError,
724                     "capabilities field is not an object"
725                 )
726                 .clone();
728                 capabilities.insert("moz:geckodriverVersion".into(), build::build_info().into());
730                 WebDriverResponse::NewSession(NewSessionResponse::new(
731                     session_id.to_string(),
732                     Value::Object(capabilities),
733                 ))
734             }
735             DeleteSession => WebDriverResponse::DeleteSession,
736             Extension(ref extension) => match extension {
737                 GetContext => WebDriverResponse::Generic(resp.into_value_response(true)?),
738                 SetContext(_) => WebDriverResponse::Void,
739                 InstallAddon(_) => WebDriverResponse::Generic(resp.into_value_response(true)?),
740                 UninstallAddon(_) => WebDriverResponse::Void,
741                 TakeFullScreenshot => WebDriverResponse::Generic(resp.into_value_response(true)?),
742             },
743         })
744     }
747 fn try_convert_to_marionette_message(
748     msg: &WebDriverMessage<GeckoExtensionRoute>,
749     browser: &Browser,
750 ) -> WebDriverResult<Option<Command>> {
751     use self::GeckoExtensionCommand::*;
752     use self::WebDriverCommand::*;
754     Ok(match msg.command {
755         AcceptAlert => Some(Command::WebDriver(MarionetteWebDriverCommand::AcceptAlert)),
756         AddCookie(ref x) => Some(Command::WebDriver(MarionetteWebDriverCommand::AddCookie(
757             x.to_marionette()?,
758         ))),
759         CloseWindow => Some(Command::WebDriver(MarionetteWebDriverCommand::CloseWindow)),
760         DeleteCookie(ref x) => Some(Command::WebDriver(
761             MarionetteWebDriverCommand::DeleteCookie(x.clone()),
762         )),
763         DeleteCookies => Some(Command::WebDriver(
764             MarionetteWebDriverCommand::DeleteCookies,
765         )),
766         DeleteSession => match browser {
767             Browser::Local(_) | Browser::Remote(_) => Some(Command::Marionette(
768                 marionette_rs::marionette::Command::DeleteSession {
769                     flags: vec![AppStatus::eForceQuit],
770                 },
771             )),
772             Browser::Existing(_) => Some(Command::WebDriver(
773                 MarionetteWebDriverCommand::DeleteSession,
774             )),
775         },
776         DismissAlert => Some(Command::WebDriver(MarionetteWebDriverCommand::DismissAlert)),
777         ElementClear(ref e) => Some(Command::WebDriver(
778             MarionetteWebDriverCommand::ElementClear {
779                 id: e.clone().to_string(),
780             },
781         )),
782         ElementClick(ref e) => Some(Command::WebDriver(
783             MarionetteWebDriverCommand::ElementClick {
784                 id: e.clone().to_string(),
785             },
786         )),
787         ElementSendKeys(ref e, ref x) => {
788             let keys = x.to_marionette()?;
789             Some(Command::WebDriver(
790                 MarionetteWebDriverCommand::ElementSendKeys {
791                     id: e.clone().to_string(),
792                     text: keys.text.clone(),
793                     value: keys.value,
794                 },
795             ))
796         }
797         ExecuteAsyncScript(ref x) => Some(Command::WebDriver(
798             MarionetteWebDriverCommand::ExecuteAsyncScript(x.to_marionette()?),
799         )),
800         ExecuteScript(ref x) => Some(Command::WebDriver(
801             MarionetteWebDriverCommand::ExecuteScript(x.to_marionette()?),
802         )),
803         FindElement(ref x) => Some(Command::WebDriver(MarionetteWebDriverCommand::FindElement(
804             x.to_marionette()?,
805         ))),
806         FindElements(ref x) => Some(Command::WebDriver(
807             MarionetteWebDriverCommand::FindElements(x.to_marionette()?),
808         )),
809         FindElementElement(ref e, ref x) => {
810             let locator = x.to_marionette()?;
811             Some(Command::WebDriver(
812                 MarionetteWebDriverCommand::FindElementElement {
813                     element: e.clone().to_string(),
814                     using: locator.using.clone(),
815                     value: locator.value,
816                 },
817             ))
818         }
819         FindElementElements(ref e, ref x) => {
820             let locator = x.to_marionette()?;
821             Some(Command::WebDriver(
822                 MarionetteWebDriverCommand::FindElementElements {
823                     element: e.clone().to_string(),
824                     using: locator.using.clone(),
825                     value: locator.value,
826                 },
827             ))
828         }
829         FindShadowRootElement(ref s, ref x) => {
830             let locator = x.to_marionette()?;
831             Some(Command::WebDriver(
832                 MarionetteWebDriverCommand::FindShadowRootElement {
833                     shadow_root: s.clone().to_string(),
834                     using: locator.using.clone(),
835                     value: locator.value,
836                 },
837             ))
838         }
839         FindShadowRootElements(ref s, ref x) => {
840             let locator = x.to_marionette()?;
841             Some(Command::WebDriver(
842                 MarionetteWebDriverCommand::FindShadowRootElements {
843                     shadow_root: s.clone().to_string(),
844                     using: locator.using.clone(),
845                     value: locator.value,
846                 },
847             ))
848         }
849         FullscreenWindow => Some(Command::WebDriver(
850             MarionetteWebDriverCommand::FullscreenWindow,
851         )),
852         Get(ref x) => Some(Command::WebDriver(MarionetteWebDriverCommand::Get(
853             x.to_marionette()?,
854         ))),
855         GetActiveElement => Some(Command::WebDriver(
856             MarionetteWebDriverCommand::GetActiveElement,
857         )),
858         GetAlertText => Some(Command::WebDriver(MarionetteWebDriverCommand::GetAlertText)),
859         GetComputedLabel(ref e) => Some(Command::WebDriver(
860             MarionetteWebDriverCommand::GetComputedLabel {
861                 id: e.clone().to_string(),
862             },
863         )),
864         GetComputedRole(ref e) => Some(Command::WebDriver(
865             MarionetteWebDriverCommand::GetComputedRole {
866                 id: e.clone().to_string(),
867             },
868         )),
869         GetCookies | GetNamedCookie(_) => {
870             Some(Command::WebDriver(MarionetteWebDriverCommand::GetCookies))
871         }
872         GetCSSValue(ref e, ref x) => Some(Command::WebDriver(
873             MarionetteWebDriverCommand::GetCSSValue {
874                 id: e.clone().to_string(),
875                 property: x.clone(),
876             },
877         )),
878         GetCurrentUrl => Some(Command::WebDriver(
879             MarionetteWebDriverCommand::GetCurrentUrl,
880         )),
881         GetElementAttribute(ref e, ref x) => Some(Command::WebDriver(
882             MarionetteWebDriverCommand::GetElementAttribute {
883                 id: e.clone().to_string(),
884                 name: x.clone(),
885             },
886         )),
887         GetElementProperty(ref e, ref x) => Some(Command::WebDriver(
888             MarionetteWebDriverCommand::GetElementProperty {
889                 id: e.clone().to_string(),
890                 name: x.clone(),
891             },
892         )),
893         GetElementRect(ref e) => Some(Command::WebDriver(
894             MarionetteWebDriverCommand::GetElementRect {
895                 id: e.clone().to_string(),
896             },
897         )),
898         GetElementTagName(ref e) => Some(Command::WebDriver(
899             MarionetteWebDriverCommand::GetElementTagName {
900                 id: e.clone().to_string(),
901             },
902         )),
903         GetElementText(ref e) => Some(Command::WebDriver(
904             MarionetteWebDriverCommand::GetElementText {
905                 id: e.clone().to_string(),
906             },
907         )),
908         GetPageSource => Some(Command::WebDriver(
909             MarionetteWebDriverCommand::GetPageSource,
910         )),
911         GetShadowRoot(ref e) => Some(Command::WebDriver(
912             MarionetteWebDriverCommand::GetShadowRoot {
913                 id: e.clone().to_string(),
914             },
915         )),
916         GetTitle => Some(Command::WebDriver(MarionetteWebDriverCommand::GetTitle)),
917         GetWindowHandle => Some(Command::WebDriver(
918             MarionetteWebDriverCommand::GetWindowHandle,
919         )),
920         GetWindowHandles => Some(Command::WebDriver(
921             MarionetteWebDriverCommand::GetWindowHandles,
922         )),
923         GetWindowRect => Some(Command::WebDriver(
924             MarionetteWebDriverCommand::GetWindowRect,
925         )),
926         GetTimeouts => Some(Command::WebDriver(MarionetteWebDriverCommand::GetTimeouts)),
927         GoBack => Some(Command::WebDriver(MarionetteWebDriverCommand::GoBack)),
928         GoForward => Some(Command::WebDriver(MarionetteWebDriverCommand::GoForward)),
929         IsDisplayed(ref e) => Some(Command::WebDriver(
930             MarionetteWebDriverCommand::IsDisplayed {
931                 id: e.clone().to_string(),
932             },
933         )),
934         IsEnabled(ref e) => Some(Command::WebDriver(MarionetteWebDriverCommand::IsEnabled {
935             id: e.clone().to_string(),
936         })),
937         IsSelected(ref e) => Some(Command::WebDriver(MarionetteWebDriverCommand::IsSelected {
938             id: e.clone().to_string(),
939         })),
940         MaximizeWindow => Some(Command::WebDriver(
941             MarionetteWebDriverCommand::MaximizeWindow,
942         )),
943         MinimizeWindow => Some(Command::WebDriver(
944             MarionetteWebDriverCommand::MinimizeWindow,
945         )),
946         NewWindow(ref x) => Some(Command::WebDriver(MarionetteWebDriverCommand::NewWindow(
947             x.to_marionette()?,
948         ))),
949         Print(ref x) => Some(Command::WebDriver(MarionetteWebDriverCommand::Print(
950             x.to_marionette()?,
951         ))),
952         Refresh => Some(Command::WebDriver(MarionetteWebDriverCommand::Refresh)),
953         ReleaseActions => Some(Command::WebDriver(
954             MarionetteWebDriverCommand::ReleaseActions,
955         )),
956         SendAlertText(ref x) => Some(Command::WebDriver(
957             MarionetteWebDriverCommand::SendAlertText(x.to_marionette()?),
958         )),
959         SetTimeouts(ref x) => Some(Command::WebDriver(MarionetteWebDriverCommand::SetTimeouts(
960             x.to_marionette()?,
961         ))),
962         SetWindowRect(ref x) => Some(Command::WebDriver(
963             MarionetteWebDriverCommand::SetWindowRect(x.to_marionette()?),
964         )),
965         SwitchToFrame(ref x) => Some(Command::WebDriver(
966             MarionetteWebDriverCommand::SwitchToFrame(x.to_marionette()?),
967         )),
968         SwitchToParentFrame => Some(Command::WebDriver(
969             MarionetteWebDriverCommand::SwitchToParentFrame,
970         )),
971         SwitchToWindow(ref x) => Some(Command::WebDriver(
972             MarionetteWebDriverCommand::SwitchToWindow(x.to_marionette()?),
973         )),
974         TakeElementScreenshot(ref e) => {
975             let screenshot = ScreenshotOptions {
976                 id: Some(e.clone().to_string()),
977                 highlights: vec![],
978                 full: false,
979             };
980             Some(Command::WebDriver(
981                 MarionetteWebDriverCommand::TakeElementScreenshot(screenshot),
982             ))
983         }
984         TakeScreenshot => {
985             let screenshot = ScreenshotOptions {
986                 id: None,
987                 highlights: vec![],
988                 full: false,
989             };
990             Some(Command::WebDriver(
991                 MarionetteWebDriverCommand::TakeScreenshot(screenshot),
992             ))
993         }
994         Extension(TakeFullScreenshot) => {
995             let screenshot = ScreenshotOptions {
996                 id: None,
997                 highlights: vec![],
998                 full: true,
999             };
1000             Some(Command::WebDriver(
1001                 MarionetteWebDriverCommand::TakeFullScreenshot(screenshot),
1002             ))
1003         }
1004         _ => None,
1005     })
1008 #[derive(Debug, PartialEq)]
1009 struct MarionetteCommand {
1010     id: MessageId,
1011     name: String,
1012     params: Map<String, Value>,
1015 impl Serialize for MarionetteCommand {
1016     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1017     where
1018         S: Serializer,
1019     {
1020         let data = (&0, &self.id, &self.name, &self.params);
1021         data.serialize(serializer)
1022     }
1025 impl MarionetteCommand {
1026     fn new(id: MessageId, name: String, params: Map<String, Value>) -> MarionetteCommand {
1027         MarionetteCommand { id, name, params }
1028     }
1030     fn encode_msg<T>(msg: T) -> WebDriverResult<String>
1031     where
1032         T: serde::Serialize,
1033     {
1034         let data = serde_json::to_string(&msg)?;
1036         Ok(format!("{}:{}", data.len(), data))
1037     }
1039     fn from_webdriver_message(
1040         id: MessageId,
1041         capabilities: &Map<String, Value>,
1042         browser: &Browser,
1043         msg: &WebDriverMessage<GeckoExtensionRoute>,
1044     ) -> WebDriverResult<String> {
1045         use self::GeckoExtensionCommand::*;
1047         if let Some(cmd) = try_convert_to_marionette_message(msg, browser)? {
1048             let req = Message::Incoming(Request(id, cmd));
1049             MarionetteCommand::encode_msg(req)
1050         } else {
1051             let (opt_name, opt_parameters) = match msg.command {
1052                 Status => panic!("Got status command that should already have been handled"),
1053                 NewSession(_) => {
1054                     let mut data = Map::new();
1055                     for (k, v) in capabilities.iter() {
1056                         data.insert(k.to_string(), serde_json::to_value(v)?);
1057                     }
1059                     (Some("WebDriver:NewSession"), Some(Ok(data)))
1060                 }
1061                 PerformActions(ref x) => {
1062                     (Some("WebDriver:PerformActions"), Some(x.to_marionette()))
1063                 }
1064                 Extension(ref extension) => match extension {
1065                     GetContext => (Some("Marionette:GetContext"), None),
1066                     InstallAddon(x) => (Some("Addon:Install"), Some(x.to_marionette())),
1067                     SetContext(x) => (Some("Marionette:SetContext"), Some(x.to_marionette())),
1068                     UninstallAddon(x) => (Some("Addon:Uninstall"), Some(x.to_marionette())),
1069                     _ => (None, None),
1070                 },
1071                 _ => (None, None),
1072             };
1074             let name = try_opt!(
1075                 opt_name,
1076                 ErrorStatus::UnsupportedOperation,
1077                 "Operation not supported"
1078             );
1079             let parameters = opt_parameters.unwrap_or_else(|| Ok(Map::new()))?;
1081             let req = MarionetteCommand::new(id, name.into(), parameters);
1082             MarionetteCommand::encode_msg(req)
1083         }
1084     }
1087 #[derive(Debug, PartialEq)]
1088 struct MarionetteResponse {
1089     id: MessageId,
1090     error: Option<MarionetteError>,
1091     result: Value,
1094 impl<'de> Deserialize<'de> for MarionetteResponse {
1095     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1096     where
1097         D: Deserializer<'de>,
1098     {
1099         #[derive(Deserialize)]
1100         struct ResponseWrapper {
1101             msg_type: u64,
1102             id: MessageId,
1103             error: Option<MarionetteError>,
1104             result: Value,
1105         }
1107         let wrapper: ResponseWrapper = Deserialize::deserialize(deserializer)?;
1109         if wrapper.msg_type != 1 {
1110             return Err(de::Error::custom(
1111                 "Expected '1' in first element of response",
1112             ));
1113         };
1115         Ok(MarionetteResponse {
1116             id: wrapper.id,
1117             error: wrapper.error,
1118             result: wrapper.result,
1119         })
1120     }
1123 impl MarionetteResponse {
1124     fn into_value_response(self, value_required: bool) -> WebDriverResult<ValueResponse> {
1125         let value: &Value = if value_required {
1126             try_opt!(
1127                 self.result.get("value"),
1128                 ErrorStatus::UnknownError,
1129                 "Failed to find value field"
1130             )
1131         } else {
1132             &self.result
1133         };
1135         Ok(ValueResponse(value.clone()))
1136     }
1139 #[derive(Debug, PartialEq, Serialize, Deserialize)]
1140 struct MarionetteError {
1141     #[serde(rename = "error")]
1142     code: String,
1143     message: String,
1144     stacktrace: Option<String>,
1147 impl From<MarionetteError> for WebDriverError {
1148     fn from(error: MarionetteError) -> WebDriverError {
1149         let status = ErrorStatus::from(error.code);
1150         let message = error.message;
1152         if let Some(stack) = error.stacktrace {
1153             WebDriverError::new_with_stack(status, message, stack)
1154         } else {
1155             WebDriverError::new(status, message)
1156         }
1157     }
1160 fn get_free_port(host: &str) -> IoResult<u16> {
1161     TcpListener::bind((host, 0))
1162         .and_then(|stream| stream.local_addr())
1163         .map(|x| x.port())
1166 struct MarionetteConnection {
1167     browser: Browser,
1168     session: MarionetteSession,
1169     stream: TcpStream,
1172 impl MarionetteConnection {
1173     fn new(
1174         host: String,
1175         mut browser: Browser,
1176         session: MarionetteSession,
1177     ) -> WebDriverResult<MarionetteConnection> {
1178         let stream = match MarionetteConnection::connect(&host, &mut browser) {
1179             Ok(stream) => stream,
1180             Err(e) => {
1181                 if let Err(e) = browser.close(true) {
1182                     error!("Failed to stop browser: {:?}", e);
1183                 }
1184                 return Err(e);
1185             }
1186         };
1187         Ok(MarionetteConnection {
1188             browser,
1189             session,
1190             stream,
1191         })
1192     }
1194     fn connect(host: &str, browser: &mut Browser) -> WebDriverResult<TcpStream> {
1195         let timeout = time::Duration::from_secs(60);
1196         let poll_interval = time::Duration::from_millis(100);
1197         let now = time::Instant::now();
1199         debug!(
1200             "Waiting {}s to connect to browser on {}",
1201             timeout.as_secs(),
1202             host,
1203         );
1205         loop {
1206             // immediately abort connection attempts if process disappears
1207             if let Browser::Local(browser) = browser {
1208                 if let Some(status) = browser.check_status() {
1209                     return Err(WebDriverError::new(
1210                         ErrorStatus::UnknownError,
1211                         format!("Process unexpectedly closed with status {}", status),
1212                     ));
1213                 }
1214             }
1216             let last_err;
1218             if let Some(port) = browser.marionette_port()? {
1219                 match MarionetteConnection::try_connect(host, port) {
1220                     Ok(stream) => {
1221                         debug!("Connection to Marionette established on {}:{}.", host, port);
1222                         browser.update_marionette_port(port);
1223                         return Ok(stream);
1224                     }
1225                     Err(e) => {
1226                         let err_str = e.to_string();
1227                         last_err = Some(err_str);
1228                     }
1229                 }
1230             } else {
1231                 last_err = Some("Failed to read marionette port".into());
1232             }
1233             if now.elapsed() < timeout {
1234                 trace!("Retrying in {:?}", poll_interval);
1235                 thread::sleep(poll_interval);
1236             } else {
1237                 return Err(WebDriverError::new(
1238                     ErrorStatus::Timeout,
1239                     last_err.unwrap_or_else(|| "Unknown error".into()),
1240                 ));
1241             }
1242         }
1243     }
1245     fn try_connect(host: &str, port: u16) -> WebDriverResult<TcpStream> {
1246         let mut stream = TcpStream::connect((host, port))?;
1247         MarionetteConnection::handshake(&mut stream)?;
1248         Ok(stream)
1249     }
1251     fn handshake(stream: &mut TcpStream) -> WebDriverResult<MarionetteHandshake> {
1252         let resp = (match stream.read_timeout() {
1253             Ok(timeout) => {
1254                 // If platform supports changing the read timeout of the stream,
1255                 // use a short one only for the handshake with Marionette. Don't
1256                 // make it shorter as 1000ms to not fail on slow connections.
1257                 stream
1258                     .set_read_timeout(Some(time::Duration::from_millis(1000)))
1259                     .ok();
1260                 let data = MarionetteConnection::read_resp(stream);
1261                 stream.set_read_timeout(timeout).ok();
1263                 data
1264             }
1265             _ => MarionetteConnection::read_resp(stream),
1266         })
1267         .map_err(|e| {
1268             WebDriverError::new(
1269                 ErrorStatus::UnknownError,
1270                 format!("Socket timeout reading Marionette handshake data: {}", e),
1271             )
1272         })?;
1274         let data = serde_json::from_str::<MarionetteHandshake>(&resp)?;
1276         if data.application_type != "gecko" {
1277             return Err(WebDriverError::new(
1278                 ErrorStatus::UnknownError,
1279                 format!("Unrecognized application type {}", data.application_type),
1280             ));
1281         }
1283         if data.protocol != 3 {
1284             return Err(WebDriverError::new(
1285                 ErrorStatus::UnknownError,
1286                 format!(
1287                     "Unsupported Marionette protocol version {}, required 3",
1288                     data.protocol
1289                 ),
1290             ));
1291         }
1293         Ok(data)
1294     }
1296     fn close(self, wait_for_shutdown: bool) -> WebDriverResult<()> {
1297         self.stream.shutdown(Shutdown::Both)?;
1298         self.browser.close(wait_for_shutdown)?;
1299         Ok(())
1300     }
1302     fn send_command(
1303         &mut self,
1304         msg: &WebDriverMessage<GeckoExtensionRoute>,
1305     ) -> WebDriverResult<WebDriverResponse> {
1306         let id = self.session.next_command_id();
1307         let enc_cmd = MarionetteCommand::from_webdriver_message(
1308             id,
1309             &self.session.capabilities,
1310             &self.browser,
1311             msg,
1312         )?;
1313         let resp_data = self.send(enc_cmd)?;
1314         let data: MarionetteResponse = serde_json::from_str(&resp_data)?;
1316         self.session.response(msg, data)
1317     }
1319     fn send(&mut self, data: String) -> WebDriverResult<String> {
1320         if self.stream.write(data.as_bytes()).is_err() {
1321             let mut err = WebDriverError::new(
1322                 ErrorStatus::UnknownError,
1323                 "Failed to write request to stream",
1324             );
1325             err.delete_session = true;
1326             return Err(err);
1327         }
1329         match MarionetteConnection::read_resp(&mut self.stream) {
1330             Ok(resp) => Ok(resp),
1331             Err(_) => {
1332                 let mut err = WebDriverError::new(
1333                     ErrorStatus::UnknownError,
1334                     "Failed to decode response from marionette",
1335                 );
1336                 err.delete_session = true;
1337                 Err(err)
1338             }
1339         }
1340     }
1342     fn read_resp(stream: &mut TcpStream) -> IoResult<String> {
1343         let mut bytes = 0usize;
1345         loop {
1346             let buf = &mut [0u8];
1347             let num_read = stream.read(buf)?;
1348             let byte = match num_read {
1349                 0 => {
1350                     return Err(IoError::new(
1351                         ErrorKind::Other,
1352                         "EOF reading marionette message",
1353                     ))
1354                 }
1355                 1 => buf[0],
1356                 _ => panic!("Expected one byte got more"),
1357             } as char;
1358             match byte {
1359                 '0'..='9' => {
1360                     bytes *= 10;
1361                     bytes += byte as usize - '0' as usize;
1362                 }
1363                 ':' => break,
1364                 _ => {}
1365             }
1366         }
1368         let buf = &mut [0u8; 8192];
1369         let mut payload = Vec::with_capacity(bytes);
1370         let mut total_read = 0;
1371         while total_read < bytes {
1372             let num_read = stream.read(buf)?;
1373             if num_read == 0 {
1374                 return Err(IoError::new(
1375                     ErrorKind::Other,
1376                     "EOF reading marionette message",
1377                 ));
1378             }
1379             total_read += num_read;
1380             for x in &buf[..num_read] {
1381                 payload.push(*x);
1382             }
1383         }
1385         // TODO(jgraham): Need to handle the error here
1386         Ok(String::from_utf8(payload).unwrap())
1387     }
1390 trait ToMarionette<T> {
1391     fn to_marionette(&self) -> WebDriverResult<T>;
1394 impl ToMarionette<Map<String, Value>> for AddonInstallParameters {
1395     fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
1396         let mut data = Map::new();
1397         data.insert("path".to_string(), serde_json::to_value(&self.path)?);
1398         if self.temporary.is_some() {
1399             data.insert(
1400                 "temporary".to_string(),
1401                 serde_json::to_value(self.temporary)?,
1402             );
1403         }
1404         Ok(data)
1405     }
1408 impl ToMarionette<Map<String, Value>> for AddonUninstallParameters {
1409     fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
1410         let mut data = Map::new();
1411         data.insert("id".to_string(), Value::String(self.id.clone()));
1412         Ok(data)
1413     }
1416 impl ToMarionette<Map<String, Value>> for GeckoContextParameters {
1417     fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
1418         let mut data = Map::new();
1419         data.insert(
1420             "value".to_owned(),
1421             serde_json::to_value(self.context.clone())?,
1422         );
1423         Ok(data)
1424     }
1427 impl ToMarionette<MarionettePrintParameters> for PrintParameters {
1428     fn to_marionette(&self) -> WebDriverResult<MarionettePrintParameters> {
1429         Ok(MarionettePrintParameters {
1430             orientation: self.orientation.to_marionette()?,
1431             scale: self.scale,
1432             background: self.background,
1433             page: self.page.to_marionette()?,
1434             margin: self.margin.to_marionette()?,
1435             page_ranges: self.page_ranges.clone(),
1436             shrink_to_fit: self.shrink_to_fit,
1437         })
1438     }
1441 impl ToMarionette<MarionettePrintOrientation> for PrintOrientation {
1442     fn to_marionette(&self) -> WebDriverResult<MarionettePrintOrientation> {
1443         Ok(match self {
1444             PrintOrientation::Landscape => MarionettePrintOrientation::Landscape,
1445             PrintOrientation::Portrait => MarionettePrintOrientation::Portrait,
1446         })
1447     }
1450 impl ToMarionette<MarionettePrintPage> for PrintPage {
1451     fn to_marionette(&self) -> WebDriverResult<MarionettePrintPage> {
1452         Ok(MarionettePrintPage {
1453             width: self.width,
1454             height: self.height,
1455         })
1456     }
1459 impl ToMarionette<MarionettePrintMargins> for PrintMargins {
1460     fn to_marionette(&self) -> WebDriverResult<MarionettePrintMargins> {
1461         Ok(MarionettePrintMargins {
1462             top: self.top,
1463             bottom: self.bottom,
1464             left: self.left,
1465             right: self.right,
1466         })
1467     }
1470 impl ToMarionette<Map<String, Value>> for ActionsParameters {
1471     fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
1472         Ok(try_opt!(
1473             serde_json::to_value(self)?.as_object(),
1474             ErrorStatus::UnknownError,
1475             "Expected an object"
1476         )
1477         .clone())
1478     }
1481 impl ToMarionette<MarionetteCookie> for AddCookieParameters {
1482     fn to_marionette(&self) -> WebDriverResult<MarionetteCookie> {
1483         Ok(MarionetteCookie {
1484             name: self.name.clone(),
1485             value: self.value.clone(),
1486             path: self.path.clone(),
1487             domain: self.domain.clone(),
1488             secure: self.secure,
1489             http_only: self.httpOnly,
1490             expiry: match &self.expiry {
1491                 Some(date) => Some(date.to_marionette()?),
1492                 None => None,
1493             },
1494             same_site: self.sameSite.clone(),
1495         })
1496     }
1499 impl ToMarionette<MarionetteDate> for Date {
1500     fn to_marionette(&self) -> WebDriverResult<MarionetteDate> {
1501         Ok(MarionetteDate(self.0))
1502     }
1505 impl ToMarionette<Map<String, Value>> for GetNamedCookieParameters {
1506     fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
1507         Ok(try_opt!(
1508             serde_json::to_value(self)?.as_object(),
1509             ErrorStatus::UnknownError,
1510             "Expected an object"
1511         )
1512         .clone())
1513     }
1516 impl ToMarionette<MarionetteUrl> for GetParameters {
1517     fn to_marionette(&self) -> WebDriverResult<MarionetteUrl> {
1518         Ok(MarionetteUrl {
1519             url: self.url.clone(),
1520         })
1521     }
1524 impl ToMarionette<MarionetteScript> for JavascriptCommandParameters {
1525     fn to_marionette(&self) -> WebDriverResult<MarionetteScript> {
1526         Ok(MarionetteScript {
1527             script: self.script.clone(),
1528             args: self.args.clone(),
1529         })
1530     }
1533 impl ToMarionette<MarionetteLocator> for LocatorParameters {
1534     fn to_marionette(&self) -> WebDriverResult<MarionetteLocator> {
1535         Ok(MarionetteLocator {
1536             using: self.using.to_marionette()?,
1537             value: self.value.clone(),
1538         })
1539     }
1542 impl ToMarionette<MarionetteSelector> for LocatorStrategy {
1543     fn to_marionette(&self) -> WebDriverResult<MarionetteSelector> {
1544         use self::LocatorStrategy::*;
1545         match self {
1546             CSSSelector => Ok(MarionetteSelector::Css),
1547             LinkText => Ok(MarionetteSelector::LinkText),
1548             PartialLinkText => Ok(MarionetteSelector::PartialLinkText),
1549             TagName => Ok(MarionetteSelector::TagName),
1550             XPath => Ok(MarionetteSelector::XPath),
1551         }
1552     }
1555 impl ToMarionette<MarionetteNewWindow> for NewWindowParameters {
1556     fn to_marionette(&self) -> WebDriverResult<MarionetteNewWindow> {
1557         Ok(MarionetteNewWindow {
1558             type_hint: self.type_hint.clone(),
1559         })
1560     }
1563 impl ToMarionette<MarionetteKeys> for SendKeysParameters {
1564     fn to_marionette(&self) -> WebDriverResult<MarionetteKeys> {
1565         Ok(MarionetteKeys {
1566             text: self.text.clone(),
1567             value: self
1568                 .text
1569                 .chars()
1570                 .map(|x| x.to_string())
1571                 .collect::<Vec<String>>(),
1572         })
1573     }
1576 impl ToMarionette<MarionetteFrame> for SwitchToFrameParameters {
1577     fn to_marionette(&self) -> WebDriverResult<MarionetteFrame> {
1578         Ok(match &self.id {
1579             Some(x) => match x {
1580                 FrameId::Short(n) => MarionetteFrame::Index(*n),
1581                 FrameId::Element(el) => MarionetteFrame::Element(el.0.clone()),
1582             },
1583             None => MarionetteFrame::Parent,
1584         })
1585     }
1588 impl ToMarionette<Window> for SwitchToWindowParameters {
1589     fn to_marionette(&self) -> WebDriverResult<Window> {
1590         Ok(Window {
1591             handle: self.handle.clone(),
1592         })
1593     }
1596 impl ToMarionette<MarionetteTimeouts> for TimeoutsParameters {
1597     fn to_marionette(&self) -> WebDriverResult<MarionetteTimeouts> {
1598         Ok(MarionetteTimeouts {
1599             implicit: self.implicit,
1600             page_load: self.page_load,
1601             script: self.script,
1602         })
1603     }
1606 impl ToMarionette<MarionetteWebElement> for WebElement {
1607     fn to_marionette(&self) -> WebDriverResult<MarionetteWebElement> {
1608         Ok(MarionetteWebElement {
1609             element: self.to_string(),
1610         })
1611     }
1614 impl ToMarionette<MarionetteWindowRect> for WindowRectParameters {
1615     fn to_marionette(&self) -> WebDriverResult<MarionetteWindowRect> {
1616         Ok(MarionetteWindowRect {
1617             x: self.x,
1618             y: self.y,
1619             width: self.width,
1620             height: self.height,
1621         })
1622     }