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};
7 use crate::capabilities::{FirefoxCapabilities, FirefoxOptions, ProfileType};
9 AddonInstallParameters, AddonUninstallParameters, GeckoContextParameters,
10 GeckoExtensionCommand, GeckoExtensionRoute,
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;
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")]
77 #[serde(rename = "applicationType")]
78 application_type: String,
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,
100 pub(crate) struct MarionetteHandler {
101 connection: Mutex<Option<MarionetteConnection>>,
102 settings: MarionetteSettings,
105 impl MarionetteHandler {
106 pub(crate) fn new(settings: MarionetteSettings) -> MarionetteHandler {
108 connection: Mutex::new(None),
113 fn create_connection(
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)?
124 ErrorStatus::SessionNotCreated,
125 "Unable to find a matching set of capabilities",
129 let options = FirefoxOptions::from_capabilities(
130 fx_capabilities.chosen_binary.clone(),
134 (capabilities, options)
137 if let Some(l) = options.log.level {
138 logging::set_max_level(l);
141 let marionette_host = self.settings.host.to_owned();
142 let marionette_port = match self.settings.port {
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
148 let can_use_profile: bool = options.android.is_none()
149 && options.profile != ProfileType::Named
150 && !self.settings.connect_existing
152 .browser_version(&capabilities)
157 .compare_browser_version(&v, ">=95")
166 get_free_port(&marionette_host)?
171 let websocket_port = if options.use_websocket {
172 Some(self.settings.websocket_port)
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",
188 Browser::Remote(RemoteBrowser::new(
192 self.settings.profile_root.as_deref(),
194 } else if !self.settings.connect_existing {
195 Browser::Local(LocalBrowser::new(
198 self.settings.jsdebugger,
199 self.settings.profile_root.as_deref(),
202 Browser::Existing(marionette_port)
204 let session = MarionetteSession::new(session_id, capabilities);
205 MarionetteConnection::new(marionette_host, browser, session)
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)
219 impl WebDriverHandler<GeckoExtensionRoute> for MarionetteHandler {
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
231 .map(|ref connection| {
234 .map(|_| (false, "Session already started"))
235 .unwrap_or((true, ""))
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(
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);
253 return Err(WebDriverError::new(
254 ErrorStatus::InvalidSessionId,
255 "Tried to run command without establishing a connection",
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;
269 Err(_) => Err(WebDriverError::new(
270 ErrorStatus::UnknownError,
271 "Failed to aquire Marionette connection",
276 fn teardown_session(&mut self, kind: SessionTeardownKind) {
277 let wait_for_shutdown = match kind {
278 SessionTeardownKind::Deleted => true,
279 SessionTeardownKind::NotDeleted => false,
281 self.close_connection(wait_for_shutdown);
285 impl Drop for MarionetteHandler {
287 self.close_connection(false);
291 struct MarionetteSession {
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();
301 session_id: initital_id,
309 msg: &WebDriverMessage<GeckoExtensionRoute>,
310 resp: &MarionetteResponse,
311 ) -> WebDriverResult<()> {
312 if let NewSession(_) = msg.command {
313 let session_id = try_opt!(
315 resp.result.get("sessionId"),
316 ErrorStatus::SessionNotCreated,
317 "Unable to get session id"
320 ErrorStatus::SessionNotCreated,
321 "Unable to convert session id to string"
323 self.session_id = session_id.to_string();
328 /// Converts a Marionette JSON response into a `WebElement`.
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> {
334 json_data.as_object(),
335 ErrorStatus::UnknownError,
336 "Failed to convert data to an object"
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"
350 ErrorStatus::UnknownError,
351 "Failed to convert web element reference value to string"
357 /// Converts a Marionette JSON response into a `ShadowRoot`.
358 fn to_shadow_root(&self, json_data: &Value) -> WebDriverResult<ShadowRoot> {
360 json_data.as_object(),
361 ErrorStatus::UnknownError,
362 "Failed to convert data to an object"
365 let shadow_root = data.get(SHADOW_KEY);
367 let value = try_opt!(
369 ErrorStatus::UnknownError,
370 "Failed to extract shadow root from Marionette response"
374 ErrorStatus::UnknownError,
375 "Failed to convert shadow root reference value to string"
381 fn next_command_id(&mut self) -> MessageId {
382 self.command_id += 1;
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,
397 "Marionette responses arrived out of sequence, expected {}, got {}",
398 self.command_id, resp.id
403 if let Some(error) = resp.error {
404 return Err(error.into());
407 self.update(msg, &resp)?;
409 Ok(match msg.command {
410 // Everything that doesn't have a response value
418 | SwitchToParentFrame
427 | ElementSendKeys(_, _)
429 | ReleaseActions => WebDriverResponse::Void,
430 // Things that simply return the contents of the marionette "value" property
437 | GetElementAttribute(_, _)
438 | GetElementProperty(_, _)
441 | GetElementTagName(_)
442 | GetComputedLabel(_)
446 | ExecuteAsyncScript(_)
450 | TakeElementScreenshot(_) => {
451 WebDriverResponse::Generic(resp.into_value_response(true)?)
454 let script = match try_opt!(
455 resp.result.get("script"),
456 ErrorStatus::UnknownError,
457 "Missing field: script"
462 ErrorStatus::UnknownError,
463 "Failed to interpret script timeout duration as u64"
466 let page_load = try_opt!(
468 resp.result.get("pageLoad"),
469 ErrorStatus::UnknownError,
470 "Missing field: pageLoad"
473 ErrorStatus::UnknownError,
474 "Failed to interpret page load duration as u64"
476 let implicit = try_opt!(
478 resp.result.get("implicit"),
479 ErrorStatus::UnknownError,
480 "Missing field: implicit"
483 ErrorStatus::UnknownError,
484 "Failed to interpret implicit search duration as u64"
487 WebDriverResponse::Timeouts(TimeoutsResponse {
493 Status => panic!("Got status command that should already have been handled"),
494 GetWindowHandles => WebDriverResponse::Generic(resp.into_value_response(false)?),
496 let handle: String = try_opt!(
498 resp.result.get("handle"),
499 ErrorStatus::UnknownError,
500 "Failed to find handle field"
503 ErrorStatus::UnknownError,
504 "Failed to interpret handle as string"
507 let typ: String = try_opt!(
509 resp.result.get("type"),
510 ErrorStatus::UnknownError,
511 "Failed to find type field"
514 ErrorStatus::UnknownError,
515 "Failed to interpret type as string"
519 WebDriverResponse::NewWindow(NewWindowResponse { handle, typ })
523 resp.result.as_array(),
524 ErrorStatus::UnknownError,
525 "Failed to interpret value as array"
532 ErrorStatus::UnknownError,
533 "Failed to interpret window handle as string"
537 .collect::<Result<Vec<_>, _>>()?;
538 WebDriverResponse::CloseWindow(CloseWindowResponse(handles))
540 GetElementRect(_) => {
543 resp.result.get("x"),
544 ErrorStatus::UnknownError,
545 "Failed to find x field"
548 ErrorStatus::UnknownError,
549 "Failed to interpret x as float"
554 resp.result.get("y"),
555 ErrorStatus::UnknownError,
556 "Failed to find y field"
559 ErrorStatus::UnknownError,
560 "Failed to interpret y as float"
563 let width = try_opt!(
565 resp.result.get("width"),
566 ErrorStatus::UnknownError,
567 "Failed to find width field"
570 ErrorStatus::UnknownError,
571 "Failed to interpret width as float"
574 let height = try_opt!(
576 resp.result.get("height"),
577 ErrorStatus::UnknownError,
578 "Failed to find height field"
581 ErrorStatus::UnknownError,
582 "Failed to interpret width as float"
585 let rect = ElementRectResponse {
591 WebDriverResponse::ElementRect(rect)
593 FullscreenWindow | MinimizeWindow | MaximizeWindow | GetWindowRect
594 | SetWindowRect(_) => {
595 let width = try_opt!(
597 resp.result.get("width"),
598 ErrorStatus::UnknownError,
599 "Failed to find width field"
602 ErrorStatus::UnknownError,
603 "Failed to interpret width as positive integer"
606 let height = try_opt!(
608 resp.result.get("height"),
609 ErrorStatus::UnknownError,
610 "Failed to find heigenht field"
613 ErrorStatus::UnknownError,
614 "Failed to interpret height as positive integer"
619 resp.result.get("x"),
620 ErrorStatus::UnknownError,
621 "Failed to find x field"
624 ErrorStatus::UnknownError,
625 "Failed to interpret x as integer"
630 resp.result.get("y"),
631 ErrorStatus::UnknownError,
632 "Failed to find y field"
635 ErrorStatus::UnknownError,
636 "Failed to interpret y as integer"
639 let rect = WindowRectResponse {
643 height: height as i32,
645 WebDriverResponse::WindowRect(rect)
648 let cookies: Vec<Cookie> = serde_json::from_value(resp.result)?;
649 WebDriverResponse::Cookies(CookiesResponse(cookies))
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!(
656 ErrorStatus::NoSuchCookie,
657 format!("No cookie with name {}", name)
659 WebDriverResponse::Cookie(CookieResponse(cookie))
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"
667 WebDriverResponse::Generic(ValueResponse(serde_json::to_value(element)?))
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"
675 let elements = element_vec
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(
684 .map(|x| serde_json::to_value(x).unwrap())
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"
694 WebDriverResponse::Generic(ValueResponse(serde_json::to_value(shadow_root)?))
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"
702 WebDriverResponse::Generic(ValueResponse(serde_json::to_value(element)?))
705 let session_id = try_opt!(
707 resp.result.get("sessionId"),
708 ErrorStatus::InvalidSessionId,
709 "Failed to find sessionId field"
712 ErrorStatus::InvalidSessionId,
713 "sessionId is not a string"
716 let mut capabilities = try_opt!(
718 resp.result.get("capabilities"),
719 ErrorStatus::UnknownError,
720 "Failed to find capabilities field"
723 ErrorStatus::UnknownError,
724 "capabilities field is not an object"
728 capabilities.insert("moz:geckodriverVersion".into(), build::build_info().into());
730 WebDriverResponse::NewSession(NewSessionResponse::new(
731 session_id.to_string(),
732 Value::Object(capabilities),
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)?),
747 fn try_convert_to_marionette_message(
748 msg: &WebDriverMessage<GeckoExtensionRoute>,
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(
759 CloseWindow => Some(Command::WebDriver(MarionetteWebDriverCommand::CloseWindow)),
760 DeleteCookie(ref x) => Some(Command::WebDriver(
761 MarionetteWebDriverCommand::DeleteCookie(x.clone()),
763 DeleteCookies => Some(Command::WebDriver(
764 MarionetteWebDriverCommand::DeleteCookies,
766 DeleteSession => match browser {
767 Browser::Local(_) | Browser::Remote(_) => Some(Command::Marionette(
768 marionette_rs::marionette::Command::DeleteSession {
769 flags: vec![AppStatus::eForceQuit],
772 Browser::Existing(_) => Some(Command::WebDriver(
773 MarionetteWebDriverCommand::DeleteSession,
776 DismissAlert => Some(Command::WebDriver(MarionetteWebDriverCommand::DismissAlert)),
777 ElementClear(ref e) => Some(Command::WebDriver(
778 MarionetteWebDriverCommand::ElementClear {
779 id: e.clone().to_string(),
782 ElementClick(ref e) => Some(Command::WebDriver(
783 MarionetteWebDriverCommand::ElementClick {
784 id: e.clone().to_string(),
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(),
797 ExecuteAsyncScript(ref x) => Some(Command::WebDriver(
798 MarionetteWebDriverCommand::ExecuteAsyncScript(x.to_marionette()?),
800 ExecuteScript(ref x) => Some(Command::WebDriver(
801 MarionetteWebDriverCommand::ExecuteScript(x.to_marionette()?),
803 FindElement(ref x) => Some(Command::WebDriver(MarionetteWebDriverCommand::FindElement(
806 FindElements(ref x) => Some(Command::WebDriver(
807 MarionetteWebDriverCommand::FindElements(x.to_marionette()?),
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,
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,
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,
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,
849 FullscreenWindow => Some(Command::WebDriver(
850 MarionetteWebDriverCommand::FullscreenWindow,
852 Get(ref x) => Some(Command::WebDriver(MarionetteWebDriverCommand::Get(
855 GetActiveElement => Some(Command::WebDriver(
856 MarionetteWebDriverCommand::GetActiveElement,
858 GetAlertText => Some(Command::WebDriver(MarionetteWebDriverCommand::GetAlertText)),
859 GetComputedLabel(ref e) => Some(Command::WebDriver(
860 MarionetteWebDriverCommand::GetComputedLabel {
861 id: e.clone().to_string(),
864 GetComputedRole(ref e) => Some(Command::WebDriver(
865 MarionetteWebDriverCommand::GetComputedRole {
866 id: e.clone().to_string(),
869 GetCookies | GetNamedCookie(_) => {
870 Some(Command::WebDriver(MarionetteWebDriverCommand::GetCookies))
872 GetCSSValue(ref e, ref x) => Some(Command::WebDriver(
873 MarionetteWebDriverCommand::GetCSSValue {
874 id: e.clone().to_string(),
878 GetCurrentUrl => Some(Command::WebDriver(
879 MarionetteWebDriverCommand::GetCurrentUrl,
881 GetElementAttribute(ref e, ref x) => Some(Command::WebDriver(
882 MarionetteWebDriverCommand::GetElementAttribute {
883 id: e.clone().to_string(),
887 GetElementProperty(ref e, ref x) => Some(Command::WebDriver(
888 MarionetteWebDriverCommand::GetElementProperty {
889 id: e.clone().to_string(),
893 GetElementRect(ref e) => Some(Command::WebDriver(
894 MarionetteWebDriverCommand::GetElementRect {
895 id: e.clone().to_string(),
898 GetElementTagName(ref e) => Some(Command::WebDriver(
899 MarionetteWebDriverCommand::GetElementTagName {
900 id: e.clone().to_string(),
903 GetElementText(ref e) => Some(Command::WebDriver(
904 MarionetteWebDriverCommand::GetElementText {
905 id: e.clone().to_string(),
908 GetPageSource => Some(Command::WebDriver(
909 MarionetteWebDriverCommand::GetPageSource,
911 GetShadowRoot(ref e) => Some(Command::WebDriver(
912 MarionetteWebDriverCommand::GetShadowRoot {
913 id: e.clone().to_string(),
916 GetTitle => Some(Command::WebDriver(MarionetteWebDriverCommand::GetTitle)),
917 GetWindowHandle => Some(Command::WebDriver(
918 MarionetteWebDriverCommand::GetWindowHandle,
920 GetWindowHandles => Some(Command::WebDriver(
921 MarionetteWebDriverCommand::GetWindowHandles,
923 GetWindowRect => Some(Command::WebDriver(
924 MarionetteWebDriverCommand::GetWindowRect,
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(),
934 IsEnabled(ref e) => Some(Command::WebDriver(MarionetteWebDriverCommand::IsEnabled {
935 id: e.clone().to_string(),
937 IsSelected(ref e) => Some(Command::WebDriver(MarionetteWebDriverCommand::IsSelected {
938 id: e.clone().to_string(),
940 MaximizeWindow => Some(Command::WebDriver(
941 MarionetteWebDriverCommand::MaximizeWindow,
943 MinimizeWindow => Some(Command::WebDriver(
944 MarionetteWebDriverCommand::MinimizeWindow,
946 NewWindow(ref x) => Some(Command::WebDriver(MarionetteWebDriverCommand::NewWindow(
949 Print(ref x) => Some(Command::WebDriver(MarionetteWebDriverCommand::Print(
952 Refresh => Some(Command::WebDriver(MarionetteWebDriverCommand::Refresh)),
953 ReleaseActions => Some(Command::WebDriver(
954 MarionetteWebDriverCommand::ReleaseActions,
956 SendAlertText(ref x) => Some(Command::WebDriver(
957 MarionetteWebDriverCommand::SendAlertText(x.to_marionette()?),
959 SetTimeouts(ref x) => Some(Command::WebDriver(MarionetteWebDriverCommand::SetTimeouts(
962 SetWindowRect(ref x) => Some(Command::WebDriver(
963 MarionetteWebDriverCommand::SetWindowRect(x.to_marionette()?),
965 SwitchToFrame(ref x) => Some(Command::WebDriver(
966 MarionetteWebDriverCommand::SwitchToFrame(x.to_marionette()?),
968 SwitchToParentFrame => Some(Command::WebDriver(
969 MarionetteWebDriverCommand::SwitchToParentFrame,
971 SwitchToWindow(ref x) => Some(Command::WebDriver(
972 MarionetteWebDriverCommand::SwitchToWindow(x.to_marionette()?),
974 TakeElementScreenshot(ref e) => {
975 let screenshot = ScreenshotOptions {
976 id: Some(e.clone().to_string()),
980 Some(Command::WebDriver(
981 MarionetteWebDriverCommand::TakeElementScreenshot(screenshot),
985 let screenshot = ScreenshotOptions {
990 Some(Command::WebDriver(
991 MarionetteWebDriverCommand::TakeScreenshot(screenshot),
994 Extension(TakeFullScreenshot) => {
995 let screenshot = ScreenshotOptions {
1000 Some(Command::WebDriver(
1001 MarionetteWebDriverCommand::TakeFullScreenshot(screenshot),
1008 #[derive(Debug, PartialEq)]
1009 struct MarionetteCommand {
1012 params: Map<String, Value>,
1015 impl Serialize for MarionetteCommand {
1016 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1020 let data = (&0, &self.id, &self.name, &self.params);
1021 data.serialize(serializer)
1025 impl MarionetteCommand {
1026 fn new(id: MessageId, name: String, params: Map<String, Value>) -> MarionetteCommand {
1027 MarionetteCommand { id, name, params }
1030 fn encode_msg<T>(msg: T) -> WebDriverResult<String>
1032 T: serde::Serialize,
1034 let data = serde_json::to_string(&msg)?;
1036 Ok(format!("{}:{}", data.len(), data))
1039 fn from_webdriver_message(
1041 capabilities: &Map<String, Value>,
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)
1051 let (opt_name, opt_parameters) = match msg.command {
1052 Status => panic!("Got status command that should already have been handled"),
1054 let mut data = Map::new();
1055 for (k, v) in capabilities.iter() {
1056 data.insert(k.to_string(), serde_json::to_value(v)?);
1059 (Some("WebDriver:NewSession"), Some(Ok(data)))
1061 PerformActions(ref x) => {
1062 (Some("WebDriver:PerformActions"), Some(x.to_marionette()))
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())),
1074 let name = try_opt!(
1076 ErrorStatus::UnsupportedOperation,
1077 "Operation not supported"
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)
1087 #[derive(Debug, PartialEq)]
1088 struct MarionetteResponse {
1090 error: Option<MarionetteError>,
1094 impl<'de> Deserialize<'de> for MarionetteResponse {
1095 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1097 D: Deserializer<'de>,
1099 #[derive(Deserialize)]
1100 struct ResponseWrapper {
1103 error: Option<MarionetteError>,
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",
1115 Ok(MarionetteResponse {
1117 error: wrapper.error,
1118 result: wrapper.result,
1123 impl MarionetteResponse {
1124 fn into_value_response(self, value_required: bool) -> WebDriverResult<ValueResponse> {
1125 let value: &Value = if value_required {
1127 self.result.get("value"),
1128 ErrorStatus::UnknownError,
1129 "Failed to find value field"
1135 Ok(ValueResponse(value.clone()))
1139 #[derive(Debug, PartialEq, Serialize, Deserialize)]
1140 struct MarionetteError {
1141 #[serde(rename = "error")]
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)
1155 WebDriverError::new(status, message)
1160 fn get_free_port(host: &str) -> IoResult<u16> {
1161 TcpListener::bind((host, 0))
1162 .and_then(|stream| stream.local_addr())
1166 struct MarionetteConnection {
1168 session: MarionetteSession,
1172 impl MarionetteConnection {
1175 mut browser: Browser,
1176 session: MarionetteSession,
1177 ) -> WebDriverResult<MarionetteConnection> {
1178 let stream = match MarionetteConnection::connect(&host, &mut browser) {
1179 Ok(stream) => stream,
1181 if let Err(e) = browser.close(true) {
1182 error!("Failed to stop browser: {:?}", e);
1187 Ok(MarionetteConnection {
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();
1200 "Waiting {}s to connect to browser on {}",
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),
1218 if let Some(port) = browser.marionette_port()? {
1219 match MarionetteConnection::try_connect(host, port) {
1221 debug!("Connection to Marionette established on {}:{}.", host, port);
1222 browser.update_marionette_port(port);
1226 let err_str = e.to_string();
1227 last_err = Some(err_str);
1231 last_err = Some("Failed to read marionette port".into());
1233 if now.elapsed() < timeout {
1234 trace!("Retrying in {:?}", poll_interval);
1235 thread::sleep(poll_interval);
1237 return Err(WebDriverError::new(
1238 ErrorStatus::Timeout,
1239 last_err.unwrap_or_else(|| "Unknown error".into()),
1245 fn try_connect(host: &str, port: u16) -> WebDriverResult<TcpStream> {
1246 let mut stream = TcpStream::connect((host, port))?;
1247 MarionetteConnection::handshake(&mut stream)?;
1251 fn handshake(stream: &mut TcpStream) -> WebDriverResult<MarionetteHandshake> {
1252 let resp = (match stream.read_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.
1258 .set_read_timeout(Some(time::Duration::from_millis(1000)))
1260 let data = MarionetteConnection::read_resp(stream);
1261 stream.set_read_timeout(timeout).ok();
1265 _ => MarionetteConnection::read_resp(stream),
1268 WebDriverError::new(
1269 ErrorStatus::UnknownError,
1270 format!("Socket timeout reading Marionette handshake data: {}", e),
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),
1283 if data.protocol != 3 {
1284 return Err(WebDriverError::new(
1285 ErrorStatus::UnknownError,
1287 "Unsupported Marionette protocol version {}, required 3",
1296 fn close(self, wait_for_shutdown: bool) -> WebDriverResult<()> {
1297 self.stream.shutdown(Shutdown::Both)?;
1298 self.browser.close(wait_for_shutdown)?;
1304 msg: &WebDriverMessage<GeckoExtensionRoute>,
1305 ) -> WebDriverResult<WebDriverResponse> {
1306 let id = self.session.next_command_id();
1307 let enc_cmd = MarionetteCommand::from_webdriver_message(
1309 &self.session.capabilities,
1313 let resp_data = self.send(enc_cmd)?;
1314 let data: MarionetteResponse = serde_json::from_str(&resp_data)?;
1316 self.session.response(msg, data)
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",
1325 err.delete_session = true;
1329 match MarionetteConnection::read_resp(&mut self.stream) {
1330 Ok(resp) => Ok(resp),
1332 let mut err = WebDriverError::new(
1333 ErrorStatus::UnknownError,
1334 "Failed to decode response from marionette",
1336 err.delete_session = true;
1342 fn read_resp(stream: &mut TcpStream) -> IoResult<String> {
1343 let mut bytes = 0usize;
1346 let buf = &mut [0u8];
1347 let num_read = stream.read(buf)?;
1348 let byte = match num_read {
1350 return Err(IoError::new(
1352 "EOF reading marionette message",
1356 _ => panic!("Expected one byte got more"),
1361 bytes += byte as usize - '0' as usize;
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)?;
1374 return Err(IoError::new(
1376 "EOF reading marionette message",
1379 total_read += num_read;
1380 for x in &buf[..num_read] {
1385 // TODO(jgraham): Need to handle the error here
1386 Ok(String::from_utf8(payload).unwrap())
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() {
1400 "temporary".to_string(),
1401 serde_json::to_value(self.temporary)?,
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()));
1416 impl ToMarionette<Map<String, Value>> for GeckoContextParameters {
1417 fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
1418 let mut data = Map::new();
1421 serde_json::to_value(self.context.clone())?,
1427 impl ToMarionette<MarionettePrintParameters> for PrintParameters {
1428 fn to_marionette(&self) -> WebDriverResult<MarionettePrintParameters> {
1429 Ok(MarionettePrintParameters {
1430 orientation: self.orientation.to_marionette()?,
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,
1441 impl ToMarionette<MarionettePrintOrientation> for PrintOrientation {
1442 fn to_marionette(&self) -> WebDriverResult<MarionettePrintOrientation> {
1444 PrintOrientation::Landscape => MarionettePrintOrientation::Landscape,
1445 PrintOrientation::Portrait => MarionettePrintOrientation::Portrait,
1450 impl ToMarionette<MarionettePrintPage> for PrintPage {
1451 fn to_marionette(&self) -> WebDriverResult<MarionettePrintPage> {
1452 Ok(MarionettePrintPage {
1454 height: self.height,
1459 impl ToMarionette<MarionettePrintMargins> for PrintMargins {
1460 fn to_marionette(&self) -> WebDriverResult<MarionettePrintMargins> {
1461 Ok(MarionettePrintMargins {
1463 bottom: self.bottom,
1470 impl ToMarionette<Map<String, Value>> for ActionsParameters {
1471 fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
1473 serde_json::to_value(self)?.as_object(),
1474 ErrorStatus::UnknownError,
1475 "Expected an object"
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()?),
1494 same_site: self.sameSite.clone(),
1499 impl ToMarionette<MarionetteDate> for Date {
1500 fn to_marionette(&self) -> WebDriverResult<MarionetteDate> {
1501 Ok(MarionetteDate(self.0))
1505 impl ToMarionette<Map<String, Value>> for GetNamedCookieParameters {
1506 fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> {
1508 serde_json::to_value(self)?.as_object(),
1509 ErrorStatus::UnknownError,
1510 "Expected an object"
1516 impl ToMarionette<MarionetteUrl> for GetParameters {
1517 fn to_marionette(&self) -> WebDriverResult<MarionetteUrl> {
1519 url: self.url.clone(),
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(),
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(),
1542 impl ToMarionette<MarionetteSelector> for LocatorStrategy {
1543 fn to_marionette(&self) -> WebDriverResult<MarionetteSelector> {
1544 use self::LocatorStrategy::*;
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),
1555 impl ToMarionette<MarionetteNewWindow> for NewWindowParameters {
1556 fn to_marionette(&self) -> WebDriverResult<MarionetteNewWindow> {
1557 Ok(MarionetteNewWindow {
1558 type_hint: self.type_hint.clone(),
1563 impl ToMarionette<MarionetteKeys> for SendKeysParameters {
1564 fn to_marionette(&self) -> WebDriverResult<MarionetteKeys> {
1566 text: self.text.clone(),
1570 .map(|x| x.to_string())
1571 .collect::<Vec<String>>(),
1576 impl ToMarionette<MarionetteFrame> for SwitchToFrameParameters {
1577 fn to_marionette(&self) -> WebDriverResult<MarionetteFrame> {
1579 Some(x) => match x {
1580 FrameId::Short(n) => MarionetteFrame::Index(*n),
1581 FrameId::Element(el) => MarionetteFrame::Element(el.0.clone()),
1583 None => MarionetteFrame::Parent,
1588 impl ToMarionette<Window> for SwitchToWindowParameters {
1589 fn to_marionette(&self) -> WebDriverResult<Window> {
1591 handle: self.handle.clone(),
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,
1606 impl ToMarionette<MarionetteWebElement> for WebElement {
1607 fn to_marionette(&self) -> WebDriverResult<MarionetteWebElement> {
1608 Ok(MarionetteWebElement {
1609 element: self.to_string(),
1614 impl ToMarionette<MarionetteWindowRect> for WindowRectParameters {
1615 fn to_marionette(&self) -> WebDriverResult<MarionetteWindowRect> {
1616 Ok(MarionetteWindowRect {
1620 height: self.height,