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