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