Bug 1824751 [wpt PR 39215] - [FLEDGE] Add "use strict" to FLEDGE WPT tests' JS script...
[gecko.git] / testing / geckodriver / src / browser.rs
blob7d99d9b8ea0dc20ae27695b21a771446538d32cb
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 use crate::android::AndroidHandler;
6 use crate::capabilities::{FirefoxOptions, ProfileType};
7 use crate::logging;
8 use crate::prefs;
9 use mozprofile::preferences::Pref;
10 use mozprofile::profile::{PrefFile, Profile};
11 use mozrunner::runner::{FirefoxProcess, FirefoxRunner, Runner, RunnerProcess};
12 use std::fs;
13 use std::io::Read;
14 use std::path::{Path, PathBuf};
15 use std::time;
16 use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult};
18 /// A running Gecko instance.
19 #[derive(Debug)]
20 #[allow(clippy::large_enum_variant)]
21 pub(crate) enum Browser {
22     Local(LocalBrowser),
23     Remote(RemoteBrowser),
25     /// An existing browser instance not controlled by GeckoDriver
26     Existing(u16),
29 impl Browser {
30     pub(crate) fn close(self, wait_for_shutdown: bool) -> WebDriverResult<()> {
31         match self {
32             Browser::Local(x) => x.close(wait_for_shutdown),
33             Browser::Remote(x) => x.close(),
34             Browser::Existing(_) => Ok(()),
35         }
36     }
38     pub(crate) fn marionette_port(&mut self) -> WebDriverResult<Option<u16>> {
39         match self {
40             Browser::Local(x) => x.marionette_port(),
41             Browser::Remote(x) => x.marionette_port(),
42             Browser::Existing(x) => Ok(Some(*x)),
43         }
44     }
46     pub(crate) fn update_marionette_port(&mut self, port: u16) {
47         match self {
48             Browser::Local(x) => x.update_marionette_port(port),
49             Browser::Remote(x) => x.update_marionette_port(port),
50             Browser::Existing(x) => {
51                 if port != *x {
52                     error!(
53                         "Cannot re-assign Marionette port when connected to an existing browser"
54                     );
55                 }
56             }
57         }
58     }
61 #[derive(Debug)]
62 /// A local Firefox process, running on this (host) device.
63 pub(crate) struct LocalBrowser {
64     marionette_port: u16,
65     prefs_backup: Option<PrefsBackup>,
66     process: FirefoxProcess,
67     profile_path: Option<PathBuf>,
70 impl LocalBrowser {
71     pub(crate) fn new(
72         options: FirefoxOptions,
73         marionette_port: u16,
74         jsdebugger: bool,
75         profile_root: Option<&Path>,
76     ) -> WebDriverResult<LocalBrowser> {
77         let binary = options.binary.ok_or_else(|| {
78             WebDriverError::new(
79                 ErrorStatus::SessionNotCreated,
80                 "Expected browser binary location, but unable to find \
81              binary in default location, no \
82              'moz:firefoxOptions.binary' capability provided, and \
83              no binary flag set on the command line",
84             )
85         })?;
87         let is_custom_profile = matches!(options.profile, ProfileType::Path(_));
89         let mut profile = match options.profile {
90             ProfileType::Named => None,
91             ProfileType::Path(x) => Some(x),
92             ProfileType::Temporary => Some(Profile::new(profile_root)?),
93         };
95         let (profile_path, prefs_backup) = if let Some(ref mut profile) = profile {
96             let profile_path = profile.path.clone();
97             let prefs_backup = set_prefs(
98                 marionette_port,
99                 profile,
100                 is_custom_profile,
101                 options.prefs,
102                 jsdebugger,
103             )
104             .map_err(|e| {
105                 WebDriverError::new(
106                     ErrorStatus::SessionNotCreated,
107                     format!("Failed to set preferences: {}", e),
108                 )
109             })?;
110             (Some(profile_path), prefs_backup)
111         } else {
112             warn!("Unable to set geckodriver prefs when using a named profile");
113             (None, None)
114         };
116         let mut runner = FirefoxRunner::new(&binary, profile);
118         runner.arg("--marionette");
119         if jsdebugger {
120             runner.arg("--jsdebugger");
121         }
122         if let Some(args) = options.args.as_ref() {
123             runner.args(args);
124         }
126         // https://developer.mozilla.org/docs/Environment_variables_affecting_crash_reporting
127         runner
128             .env("MOZ_CRASHREPORTER", "1")
129             .env("MOZ_CRASHREPORTER_NO_REPORT", "1")
130             .env("MOZ_CRASHREPORTER_SHUTDOWN", "1");
132         let process = match runner.start() {
133             Ok(process) => process,
134             Err(e) => {
135                 if let Some(backup) = prefs_backup {
136                     backup.restore();
137                 }
138                 return Err(WebDriverError::new(
139                     ErrorStatus::SessionNotCreated,
140                     format!("Failed to start browser {}: {}", binary.display(), e),
141                 ));
142             }
143         };
145         Ok(LocalBrowser {
146             marionette_port,
147             prefs_backup,
148             process,
149             profile_path,
150         })
151     }
153     fn close(mut self, wait_for_shutdown: bool) -> WebDriverResult<()> {
154         if wait_for_shutdown {
155             // TODO(https://bugzil.la/1443922):
156             // Use toolkit.asyncshutdown.crash_timout pref
157             let duration = time::Duration::from_secs(70);
158             match self.process.wait(duration) {
159                 Ok(x) => debug!("Browser process stopped: {}", x),
160                 Err(e) => error!("Failed to stop browser process: {}", e),
161             }
162         }
163         self.process.kill()?;
165         // Restoring the prefs if the browser fails to stop perhaps doesn't work anyway
166         if let Some(prefs_backup) = self.prefs_backup {
167             prefs_backup.restore();
168         };
170         Ok(())
171     }
173     fn marionette_port(&mut self) -> WebDriverResult<Option<u16>> {
174         if self.marionette_port != 0 {
175             return Ok(Some(self.marionette_port));
176         }
178         if let Some(profile_path) = self.profile_path.as_ref() {
179             return Ok(read_marionette_port(profile_path));
180         }
182         // This should be impossible, but it isn't enforced
183         Err(WebDriverError::new(
184             ErrorStatus::SessionNotCreated,
185             "Port not known when using named profile",
186         ))
187     }
189     fn update_marionette_port(&mut self, port: u16) {
190         self.marionette_port = port;
191     }
193     pub(crate) fn check_status(&mut self) -> Option<String> {
194         match self.process.try_wait() {
195             Ok(Some(status)) => Some(
196                 status
197                     .code()
198                     .map(|c| c.to_string())
199                     .unwrap_or_else(|| "signal".into()),
200             ),
201             Ok(None) => None,
202             Err(_) => Some("{unknown}".into()),
203         }
204     }
207 fn read_marionette_port(profile_path: &Path) -> Option<u16> {
208     let port_file = profile_path.join("MarionetteActivePort");
209     let mut port_str = String::with_capacity(6);
210     let mut file = match fs::File::open(&port_file) {
211         Ok(file) => file,
212         Err(_) => {
213             trace!("Failed to open {}", &port_file.to_string_lossy());
214             return None;
215         }
216     };
217     if let Err(e) = file.read_to_string(&mut port_str) {
218         trace!("Failed to read {}: {}", &port_file.to_string_lossy(), e);
219         return None;
220     };
221     println!("Read port: {}", port_str);
222     let port = port_str.parse::<u16>().ok();
223     if port.is_none() {
224         warn!("Failed fo convert {} to u16", &port_str);
225     }
226     port
229 #[derive(Debug)]
230 /// A remote instance, running on a (target) Android device.
231 pub(crate) struct RemoteBrowser {
232     handler: AndroidHandler,
233     marionette_port: u16,
234     prefs_backup: Option<PrefsBackup>,
237 impl RemoteBrowser {
238     pub(crate) fn new(
239         options: FirefoxOptions,
240         marionette_port: u16,
241         websocket_port: Option<u16>,
242         profile_root: Option<&Path>,
243     ) -> WebDriverResult<RemoteBrowser> {
244         let android_options = options.android.unwrap();
246         let handler = AndroidHandler::new(&android_options, marionette_port, websocket_port)?;
248         // Profile management.
249         let (mut profile, is_custom_profile) = match options.profile {
250             ProfileType::Named => {
251                 return Err(WebDriverError::new(
252                     ErrorStatus::SessionNotCreated,
253                     "Cannot use a named profile on Android",
254                 ));
255             }
256             ProfileType::Path(x) => (x, true),
257             ProfileType::Temporary => (Profile::new(profile_root)?, false),
258         };
260         let prefs_backup = set_prefs(
261             handler.marionette_target_port,
262             &mut profile,
263             is_custom_profile,
264             options.prefs,
265             false,
266         )
267         .map_err(|e| {
268             WebDriverError::new(
269                 ErrorStatus::SessionNotCreated,
270                 format!("Failed to set preferences: {}", e),
271             )
272         })?;
274         handler.prepare(&profile, options.args, options.env.unwrap_or_default())?;
276         handler.launch()?;
278         Ok(RemoteBrowser {
279             handler,
280             marionette_port,
281             prefs_backup,
282         })
283     }
285     fn close(self) -> WebDriverResult<()> {
286         self.handler.force_stop()?;
288         // Restoring the prefs if the browser fails to stop perhaps doesn't work anyway
289         if let Some(prefs_backup) = self.prefs_backup {
290             prefs_backup.restore();
291         };
293         Ok(())
294     }
296     fn marionette_port(&mut self) -> WebDriverResult<Option<u16>> {
297         Ok(Some(self.marionette_port))
298     }
300     fn update_marionette_port(&mut self, port: u16) {
301         self.marionette_port = port;
302     }
305 fn set_prefs(
306     port: u16,
307     profile: &mut Profile,
308     custom_profile: bool,
309     extra_prefs: Vec<(String, Pref)>,
310     js_debugger: bool,
311 ) -> WebDriverResult<Option<PrefsBackup>> {
312     let prefs = profile.user_prefs().map_err(|_| {
313         WebDriverError::new(
314             ErrorStatus::UnknownError,
315             "Unable to read profile preferences file",
316         )
317     })?;
319     let backup_prefs = if custom_profile && prefs.path.exists() {
320         Some(PrefsBackup::new(prefs)?)
321     } else {
322         None
323     };
325     for &(name, ref value) in prefs::DEFAULT.iter() {
326         if !custom_profile || !prefs.contains_key(name) {
327             prefs.insert(name.to_string(), (*value).clone());
328         }
329     }
331     prefs.insert_slice(&extra_prefs[..]);
333     if js_debugger {
334         prefs.insert("devtools.browsertoolbox.panel", Pref::new("jsdebugger"));
335         prefs.insert("devtools.debugger.remote-enabled", Pref::new(true));
336         prefs.insert("devtools.chrome.enabled", Pref::new(true));
337         prefs.insert("devtools.debugger.prompt-connection", Pref::new(false));
338     }
340     prefs.insert("marionette.port", Pref::new(port));
341     prefs.insert("remote.log.level", logging::max_level().into());
343     prefs.write().map_err(|e| {
344         WebDriverError::new(
345             ErrorStatus::UnknownError,
346             format!("Unable to write Firefox profile: {}", e),
347         )
348     })?;
349     Ok(backup_prefs)
352 #[derive(Debug)]
353 struct PrefsBackup {
354     orig_path: PathBuf,
355     backup_path: PathBuf,
358 impl PrefsBackup {
359     fn new(prefs: &PrefFile) -> WebDriverResult<PrefsBackup> {
360         let mut prefs_backup_path = prefs.path.clone();
361         let mut counter = 0;
362         while {
363             let ext = if counter > 0 {
364                 format!("geckodriver_backup_{}", counter)
365             } else {
366                 "geckodriver_backup".to_string()
367             };
368             prefs_backup_path.set_extension(ext);
369             prefs_backup_path.exists()
370         } {
371             counter += 1
372         }
373         debug!("Backing up prefs to {:?}", prefs_backup_path);
374         fs::copy(&prefs.path, &prefs_backup_path)?;
376         Ok(PrefsBackup {
377             orig_path: prefs.path.clone(),
378             backup_path: prefs_backup_path,
379         })
380     }
382     fn restore(self) {
383         if self.backup_path.exists() {
384             let _ = fs::rename(self.backup_path, self.orig_path);
385         }
386     }
389 #[cfg(test)]
390 mod tests {
391     use super::set_prefs;
392     use crate::browser::read_marionette_port;
393     use crate::capabilities::{FirefoxOptions, ProfileType};
394     use mozprofile::preferences::{Pref, PrefValue};
395     use mozprofile::profile::Profile;
396     use serde_json::{Map, Value};
397     use std::fs::File;
398     use std::io::{Read, Write};
399     use std::path::Path;
400     use tempfile::tempdir;
402     fn example_profile() -> Value {
403         let mut profile_data = Vec::with_capacity(1024);
404         let mut profile = File::open("src/tests/profile.zip").unwrap();
405         profile.read_to_end(&mut profile_data).unwrap();
406         Value::String(base64::encode(&profile_data))
407     }
409     // This is not a pretty test, mostly due to the nature of
410     // mozprofile's and MarionetteHandler's APIs, but we have had
411     // several regressions related to remote.log.level.
412     #[test]
413     fn test_remote_log_level() {
414         let mut profile = Profile::new(None).unwrap();
415         set_prefs(2828, &mut profile, false, vec![], false).ok();
416         let user_prefs = profile.user_prefs().unwrap();
418         let pref = user_prefs.get("remote.log.level").unwrap();
419         let value = match pref.value {
420             PrefValue::String(ref s) => s,
421             _ => panic!(),
422         };
423         for (i, ch) in value.chars().enumerate() {
424             if i == 0 {
425                 assert!(ch.is_uppercase());
426             } else {
427                 assert!(ch.is_lowercase());
428             }
429         }
430     }
432     #[test]
433     fn test_prefs() {
434         let marionette_settings = Default::default();
436         let encoded_profile = example_profile();
437         let mut prefs: Map<String, Value> = Map::new();
438         prefs.insert(
439             "browser.display.background_color".into(),
440             Value::String("#00ff00".into()),
441         );
443         let mut firefox_opts = Map::new();
444         firefox_opts.insert("profile".into(), encoded_profile);
445         firefox_opts.insert("prefs".into(), Value::Object(prefs));
447         let mut caps = Map::new();
448         caps.insert("moz:firefoxOptions".into(), Value::Object(firefox_opts));
450         let opts = FirefoxOptions::from_capabilities(None, &marionette_settings, &mut caps)
451             .expect("Valid profile and prefs");
453         let mut profile = match opts.profile {
454             ProfileType::Path(profile) => profile,
455             _ => panic!("Expected ProfileType::Path"),
456         };
458         set_prefs(2828, &mut profile, true, opts.prefs, false).expect("set preferences");
460         let prefs_set = profile.user_prefs().expect("valid user preferences");
461         println!("{:#?}", prefs_set.prefs);
463         assert_eq!(
464             prefs_set.get("startup.homepage_welcome_url"),
465             Some(&Pref::new("data:text/html,PASS"))
466         );
467         assert_eq!(
468             prefs_set.get("browser.display.background_color"),
469             Some(&Pref::new("#00ff00"))
470         );
471         assert_eq!(prefs_set.get("marionette.port"), Some(&Pref::new(2828)));
472     }
474     #[test]
475     fn test_pref_backup() {
476         let mut profile = Profile::new(None).unwrap();
478         // Create some prefs in the profile
479         let initial_prefs = profile.user_prefs().unwrap();
480         initial_prefs.insert("geckodriver.example", Pref::new("example"));
481         initial_prefs.write().unwrap();
483         let prefs_path = initial_prefs.path.clone();
485         let mut conflicting_backup_path = initial_prefs.path.clone();
486         conflicting_backup_path.set_extension("geckodriver_backup");
487         println!("{:?}", conflicting_backup_path);
488         let mut file = File::create(&conflicting_backup_path).unwrap();
489         file.write_all(b"test").unwrap();
490         assert!(conflicting_backup_path.exists());
492         let mut initial_prefs_data = String::new();
493         File::open(&prefs_path)
494             .expect("Initial prefs exist")
495             .read_to_string(&mut initial_prefs_data)
496             .unwrap();
498         let backup = set_prefs(2828, &mut profile, true, vec![], false)
499             .unwrap()
500             .unwrap();
501         let user_prefs = profile.user_prefs().unwrap();
503         assert!(user_prefs.path.exists());
504         let mut backup_path = user_prefs.path.clone();
505         backup_path.set_extension("geckodriver_backup_1");
507         assert!(backup_path.exists());
509         // Ensure the actual prefs contain both the existing ones and the ones we added
510         let pref = user_prefs.get("marionette.port").unwrap();
511         assert_eq!(pref.value, PrefValue::Int(2828));
513         let pref = user_prefs.get("geckodriver.example").unwrap();
514         assert_eq!(pref.value, PrefValue::String("example".into()));
516         // Ensure the backup prefs don't contain the new settings
517         let mut backup_data = String::new();
518         File::open(&backup_path)
519             .expect("Backup prefs exist")
520             .read_to_string(&mut backup_data)
521             .unwrap();
522         assert_eq!(backup_data, initial_prefs_data);
524         backup.restore();
526         assert!(!backup_path.exists());
527         let mut final_prefs_data = String::new();
528         File::open(&prefs_path)
529             .expect("Initial prefs exist")
530             .read_to_string(&mut final_prefs_data)
531             .unwrap();
532         assert_eq!(final_prefs_data, initial_prefs_data);
533     }
535     #[test]
536     fn test_local_read_marionette_port() {
537         fn create_port_file(profile_path: &Path, data: &[u8]) {
538             let port_path = profile_path.join("MarionetteActivePort");
539             let mut file = File::create(&port_path).unwrap();
540             file.write_all(data).unwrap();
541         }
543         let profile_dir = tempdir().unwrap();
544         let profile_path = profile_dir.path();
545         assert_eq!(read_marionette_port(profile_path), None);
546         assert_eq!(read_marionette_port(profile_path), None);
547         create_port_file(profile_path, b"");
548         assert_eq!(read_marionette_port(profile_path), None);
549         create_port_file(profile_path, b"1234");
550         assert_eq!(read_marionette_port(profile_path), Some(1234));
551         create_port_file(profile_path, b"1234abc");
552         assert_eq!(read_marionette_port(profile_path), None);
553     }