1use core::panic;
6use std::cell::Cell;
7use std::collections::HashMap;
8use std::fs::{self, read_to_string};
9use std::path::{Path, PathBuf};
10use std::rc::Rc;
11use std::str::FromStr;
12#[cfg(any(target_os = "android", target_env = "ohos"))]
13use std::sync::OnceLock;
14use std::{env, fmt};
15
16use bpaf::*;
17use euclid::Size2D;
18use log::warn;
19use serde_json::Value;
20use servo::user_contents::UserStyleSheet;
21use servo::{
22 DeviceIndependentPixel, DiagnosticsLogging, DiagnosticsLoggingOption, Opts, OutputOptions,
23 PrefValue, Preferences,
24};
25use url::Url;
26
27use crate::VERSION;
28
29pub(crate) static EXPERIMENTAL_PREFS: &[&str] = &[
35 "dom_async_clipboard_enabled",
36 "dom_exec_command_enabled",
37 "dom_fontface_enabled",
38 "dom_indexeddb_enabled",
39 "dom_intersection_observer_enabled",
40 "dom_navigator_protocol_handlers_enabled",
41 "dom_notification_enabled",
42 "dom_offscreen_canvas_enabled",
43 "dom_permissions_enabled",
44 "dom_sanitizer_enabled",
45 "dom_storage_manager_api_enabled",
46 "dom_webgl2_enabled",
47 "dom_webgpu_enabled",
48 "layout_css_alpha_color_function_enabled",
49 "layout_css_attr_enabled",
50 "layout_css_ellipse_corners_enabled",
51 "layout_css_progress_function_enabled",
52 "layout_columns_enabled",
53 "layout_container_queries_enabled",
54 "layout_grid_enabled",
55 "layout_variable_fonts_enabled",
56];
57
58#[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
59#[derive(Clone)]
60pub(crate) struct ServoShellPreferences {
61 pub url: Option<String>,
63 pub device_pixel_ratio_override: Option<f32>,
65 pub clean_shutdown: bool,
67 pub no_native_titlebar: bool,
69 pub homepage: String,
71 pub searchpage: String,
74 pub headless: bool,
77 pub tracing_filter: Option<String>,
82 pub initial_window_size: Size2D<u32, DeviceIndependentPixel>,
84 pub screen_size_override: Option<Size2D<u32, DeviceIndependentPixel>>,
87 pub simulate_touch_events: bool,
89 pub output_image_path: Option<String>,
92 pub exit_after_stable_image: bool,
94 pub userscripts_directory: Option<PathBuf>,
97 pub user_stylesheets: Vec<Rc<UserStyleSheet>>,
99 pub webdriver_port: Cell<Option<u16>>,
102 pub experimental_preferences_enabled: bool,
104 #[cfg(target_env = "ohos")]
107 pub log_filter: Option<String>,
108 #[cfg(target_env = "ohos")]
110 pub log_to_file: bool,
111}
112
113impl Default for ServoShellPreferences {
114 fn default() -> Self {
115 Self {
116 clean_shutdown: false,
117 device_pixel_ratio_override: None,
118 headless: false,
119 homepage: "https://servo.org".into(),
120 initial_window_size: Size2D::new(1024, 740),
121 no_native_titlebar: true,
122 screen_size_override: None,
123 simulate_touch_events: false,
124 searchpage: "https://duckduckgo.com/html/?q=%s".into(),
125 tracing_filter: None,
126 url: None,
127 output_image_path: None,
128 exit_after_stable_image: false,
129 userscripts_directory: None,
130 user_stylesheets: Default::default(),
131 webdriver_port: Cell::new(None),
132 #[cfg(target_env = "ohos")]
133 log_filter: None,
134 #[cfg(target_env = "ohos")]
135 log_to_file: false,
136 experimental_preferences_enabled: false,
137 }
138 }
139}
140
141#[cfg(all(
142 unix,
143 not(target_os = "macos"),
144 not(target_os = "ios"),
145 not(target_os = "android"),
146 not(target_env = "ohos")
147))]
148pub fn default_config_dir() -> Option<PathBuf> {
149 let mut config_dir = ::dirs::config_dir().unwrap();
150 config_dir.push("servo");
151 config_dir.push("default");
152 Some(config_dir)
153}
154
155#[cfg(any(target_os = "android", target_env = "ohos"))]
157pub(crate) static DEFAULT_CONFIG_DIR: OnceLock<PathBuf> = OnceLock::new();
158#[cfg(any(target_os = "android", target_env = "ohos"))]
159pub fn default_config_dir() -> Option<PathBuf> {
160 DEFAULT_CONFIG_DIR.get().cloned()
161}
162
163#[cfg(target_os = "macos")]
164pub fn default_config_dir() -> Option<PathBuf> {
165 let mut config_dir = ::dirs::data_dir().unwrap();
168 config_dir.push("Servo");
169 Some(config_dir)
170}
171
172#[cfg(target_os = "windows")]
173pub fn default_config_dir() -> Option<PathBuf> {
174 let mut config_dir = ::dirs::config_dir().unwrap();
175 config_dir.push("Servo");
176 Some(config_dir)
177}
178
179fn get_preferences(prefs_files: &[PathBuf], config_dir: &Option<PathBuf>) -> Preferences {
183 if cfg!(test) {
186 return Preferences::default();
187 }
188
189 let user_prefs_path = config_dir
190 .clone()
191 .map(|path| path.join("prefs.json"))
192 .filter(|path| path.exists());
193 let user_prefs_hash = user_prefs_path.map(read_prefs_file).unwrap_or_default();
194
195 let apply_preferences =
196 |preferences: &mut Preferences, preferences_hash: HashMap<String, PrefValue>| {
197 for (key, value) in preferences_hash.iter() {
198 preferences.set_value(key, value.clone());
199 }
200 };
201
202 let mut preferences = Preferences::default();
203 apply_preferences(&mut preferences, user_prefs_hash);
204 for pref_file_path in prefs_files.iter() {
205 apply_preferences(&mut preferences, read_prefs_file(pref_file_path))
206 }
207
208 preferences
209}
210
211fn read_prefs_file<P: AsRef<Path>>(path: P) -> HashMap<String, PrefValue> {
212 read_prefs_map(&read_to_string(path).expect("Error opening user prefs"))
213}
214
215pub fn read_prefs_map(txt: &str) -> HashMap<String, PrefValue> {
216 let prefs: HashMap<String, Value> = serde_json::from_str(txt)
217 .map_err(|_| panic!("Could not parse preferences JSON"))
218 .unwrap();
219 prefs
220 .into_iter()
221 .map(|(key, value)| {
222 let value = (&value)
223 .try_into()
224 .map_err(|error| panic!("{error}"))
225 .unwrap();
226 (key, value)
227 })
228 .collect()
229}
230
231#[expect(clippy::large_enum_variant)]
232#[cfg_attr(any(target_os = "android", target_env = "ohos"), expect(dead_code))]
233pub(crate) enum ArgumentParsingResult {
234 ChromeProcess(Opts, Preferences, ServoShellPreferences),
235 ContentProcess(String),
236 Exit,
237 ErrorParsing,
238}
239
240enum ParseResolutionError {
241 InvalidFormat,
242 ZeroDimension,
243 ParseError(std::num::ParseIntError),
244}
245
246impl fmt::Display for ParseResolutionError {
247 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248 match self {
249 ParseResolutionError::InvalidFormat => write!(f, "invalid resolution format"),
250 ParseResolutionError::ZeroDimension => {
251 write!(f, "width and height must be greater than 0")
252 },
253 ParseResolutionError::ParseError(e) => write!(f, "{e}"),
254 }
255 }
256}
257
258fn parse_resolution_string(
260 string: String,
261) -> Result<Option<Size2D<u32, DeviceIndependentPixel>>, ParseResolutionError> {
262 if string.is_empty() {
263 Ok(None)
264 } else {
265 let (width, height) = string
266 .split_once(['x', 'X'])
267 .ok_or(ParseResolutionError::InvalidFormat)?;
268
269 let width = width.trim();
270 let height = height.trim();
271 if width.is_empty() || height.is_empty() {
272 return Err(ParseResolutionError::InvalidFormat);
273 }
274
275 let width = width.parse().map_err(ParseResolutionError::ParseError)?;
276 let height = height.parse().map_err(ParseResolutionError::ParseError)?;
277 if width == 0 || height == 0 {
278 return Err(ParseResolutionError::ZeroDimension);
279 }
280
281 Ok(Some(Size2D::new(width, height)))
282 }
283}
284
285fn parse_user_stylesheets(string: String) -> Result<Vec<Rc<UserStyleSheet>>, std::io::Error> {
288 let mut results = Vec::new();
289 for path_string in string.split([' ', ',']) {
290 let path = env::current_dir()?.join(path_string);
291 results.push(Rc::new(UserStyleSheet::new(
292 read_to_string(&path)?,
293 Url::from_file_path(&path).unwrap(),
294 )));
295 }
296 Ok(results)
297}
298
299fn flag_with_default_parser<S, T>(
305 short_cmd: Option<char>,
306 long_cmd: &'static str,
307 argument_help: &'static str,
308 help: &'static str,
309 default: T,
310 transform: fn(S) -> T,
311) -> impl Parser<Option<T>>
312where
313 S: FromStr + 'static,
314 <S as FromStr>::Err: fmt::Display,
315 T: Clone + 'static,
316{
317 let just_flag = if let Some(c) = short_cmd {
318 short(c).long(long_cmd)
319 } else {
320 long(long_cmd)
321 }
322 .req_flag(default)
323 .hide();
324
325 let arg = if let Some(c) = short_cmd {
326 short(c).long(long_cmd)
327 } else {
328 long(long_cmd)
329 }
330 .argument::<S>(argument_help)
331 .help(help)
332 .map(transform);
333
334 construct!([arg, just_flag]).optional()
335}
336
337fn profile() -> impl Parser<Option<OutputOptions>> {
338 flag_with_default_parser(
339 Some('p'),
340 "profile",
341 "",
342 "uses 5.0 output as standard if no argument supplied",
343 OutputOptions::Stdout(5.0),
344 |val: String| {
345 if let Ok(float) = val.parse::<f64>() {
346 OutputOptions::Stdout(float)
347 } else {
348 OutputOptions::FileName(val)
349 }
350 },
351 )
352}
353
354fn userscripts() -> impl Parser<Option<PathBuf>> {
355 let arg = long("userscripts")
356 .argument::<String>("your/directory")
357 .help("Uses userscripts in specified full path")
358 .map(PathBuf::from);
359
360 construct!([arg]).optional()
361}
362
363fn webdriver_port() -> impl Parser<Option<u16>> {
364 flag_with_default_parser(
365 None,
366 "webdriver",
367 "7000",
368 "Start remote WebDriver server on port",
369 7000,
370 |val| val,
371 )
372}
373
374fn map_debug_options(arg: String) -> Vec<String> {
375 arg.split(',').map(|s| s.to_owned()).collect()
376}
377
378#[derive(Bpaf, Clone, Debug)]
379#[bpaf(options, version(VERSION), usage("servoshell [OPTIONS] URL"))]
380struct CmdArgs {
382 #[bpaf(short('B'), long("bhm"))]
384 background_hang_monitor: bool,
385
386 #[bpaf(argument("/home/servo/resources/certs"))]
389 certificate_path: Option<PathBuf>,
390
391 #[bpaf(long)]
393 clean_shutdown: bool,
394
395 #[bpaf(argument("~/.config/servo"))]
398 config_dir: Option<PathBuf>,
399
400 #[bpaf(long)]
402 temporary_storage: bool,
403
404 #[bpaf(argument("servo-ipc-channel.abcdefg"))]
407 content_process: Option<String>,
408
409 #[bpaf(
412 short('Z'),
413 argument("layout_grid_enabled=true,dom_async_clipboard_enabled"),
414 long,
415 map(map_debug_options),
416 fallback(vec![])
417 )]
418 debug: Vec<String>,
419
420 #[bpaf(argument("1.0"))]
423 device_pixel_ratio: Option<f32>,
424
425 #[bpaf(argument("127.0.0.1:7000"))]
427 devtools: Option<String>,
428
429 #[bpaf(long)]
432 enable_experimental_web_platform_features: bool,
433
434 #[bpaf(short('x'), long)]
436 exit: bool,
437
438 #[bpaf(short('I'), long("force-ipc"))]
440 force_ipc: bool,
441
442 #[bpaf(short('f'), long)]
444 hard_fail: bool,
445
446 #[bpaf(short('z'), long)]
448 headless: bool,
449
450 #[bpaf(long("host-file"), argument("/path/to/hosts"))]
454 host_file: Option<PathBuf>,
455
456 #[bpaf(long)]
459 ignore_certificate_errors: bool,
460
461 #[bpaf(short('y'), long, argument("1"))]
463 layout_threads: Option<i64>,
464
465 #[bpaf(argument("~/.local/share/servo"))]
468 local_script_source: Option<PathBuf>,
469
470 #[cfg(target_env = "ohos")]
471 #[bpaf(argument("FILTER"))]
473 log_filter: Option<String>,
474
475 #[cfg(target_env = "ohos")]
476 #[bpaf(long)]
478 log_to_file: bool,
479
480 #[bpaf(short('M'), long)]
482 multiprocess: bool,
483
484 #[bpaf(short('b'), long)]
486 no_native_titlebar: bool,
487
488 #[bpaf(short('o'), argument("test.png"), long)]
491 output: Option<PathBuf>,
492
493 #[bpaf(external)]
496 profile: Option<OutputOptions>,
497
498 #[bpaf(argument("trace.html"), long)]
501 profiler_trace_path: Option<PathBuf>,
502
503 #[bpaf(argument("dom_bluetooth_enabled"), many)]
506 pref: Vec<String>,
507
508 #[bpaf(long, argument("/path/to/prefs.json"), many)]
511 prefs_file: Vec<PathBuf>,
512
513 #[bpaf(argument("0.25"))]
516 random_pipeline_closure_probability: Option<f32>,
517
518 random_pipeline_closure_seed: Option<usize>,
520
521 #[bpaf(short('S'), long)]
523 sandbox: bool,
524
525 shaders: Option<PathBuf>,
527
528 #[bpaf(long("screen-size"), argument::<String>("1024x768"),
531 parse(parse_resolution_string), fallback(None))]
532 screen_size_override: Option<Size2D<u32, DeviceIndependentPixel>>,
533
534 #[bpaf(long("simulate-touch-events"))]
537 simulate_touch_events: bool,
538
539 #[bpaf(long("tracing-filter"), argument("FILTER"))]
541 tracing_filter: Option<String>,
542
543 #[bpaf(long)]
545 unminify_js: bool,
546
547 #[bpaf(long)]
549 unminify_css: bool,
550
551 #[bpaf(short('u'),long,argument::<String>("NCSA mosaic/1.0 (X11;SunOS 4.1.4 sun4m"))]
554 user_agent: Option<String>,
555
556 #[bpaf(external)]
559 userscripts: Option<PathBuf>,
560
561 #[bpaf(argument::<String>("file.css"), parse(parse_user_stylesheets),
565 fallback(vec![]))]
566 user_stylesheet: Vec<Rc<UserStyleSheet>>,
567
568 #[bpaf(external)]
570 webdriver_port: Option<u16>,
571
572 #[bpaf(argument::<String>("1024x740"), parse(parse_resolution_string), fallback(None))]
575 window_size: Option<Size2D<u32, DeviceIndependentPixel>>,
576
577 #[bpaf(long)]
579 zealous_gc: bool,
580
581 #[bpaf(positional("URL"), fallback(String::from("https://www.servo.org")))]
583 url: String,
584}
585
586fn update_preferences_from_command_line_arguments(
587 preferences: &mut Preferences,
588 cmd_args: &CmdArgs,
589) {
590 if let Some(listen_address) = &cmd_args.devtools {
591 preferences.devtools_server_enabled = true;
592 preferences.devtools_server_listen_address = listen_address.clone();
593 }
594
595 if cmd_args.enable_experimental_web_platform_features {
596 for pref in EXPERIMENTAL_PREFS {
597 preferences.set_value(pref, PrefValue::Bool(true));
598 }
599 }
600
601 for pref in &cmd_args.pref {
602 let split: Vec<&str> = pref.splitn(2, '=').collect();
603 let pref_name = split[0];
604 let pref_value = PrefValue::from_booleanish_str(split.get(1).copied().unwrap_or("true"));
605 preferences.set_value(pref_name, pref_value);
606 }
607
608 if let Some(layout_threads) = cmd_args.layout_threads {
609 preferences.layout_threads = layout_threads;
610 }
611
612 if cmd_args.headless && preferences.media_glvideo_enabled {
613 warn!("GL video rendering is not supported on headless windows.");
614 preferences.media_glvideo_enabled = false;
615 }
616
617 if let Some(user_agent) = cmd_args.user_agent.clone() {
618 preferences.user_agent = user_agent;
619 }
620
621 if cmd_args.webdriver_port.is_some() {
622 preferences.dom_testing_html_input_element_select_files_enabled = true;
623 }
624
625 if cmd_args.zealous_gc {
626 #[cfg(not(feature = "debugmozjs"))]
627 warn!(
628 "The zealous-gc option requires Servo to be compiled with debug-mozjs to take effect."
629 );
630
631 preferences.js_mem_gc_zeal_level = 2;
632 preferences.js_mem_gc_zeal_frequency = 1;
633 }
634}
635
636pub(crate) fn parse_command_line_arguments<'a>(
641 args_without_binary: impl Into<Args<'a>>,
642) -> ArgumentParsingResult {
643 parse_arguments_helper(args_without_binary.into())
644}
645fn parse_arguments_helper(args_without_binary: Args) -> ArgumentParsingResult {
646 let cmd_args = cmd_args().run_inner(args_without_binary);
647 let cmd_args = match cmd_args {
648 Ok(cmd_args) => cmd_args,
649 Err(error) => {
650 if cfg!(target_os = "android") || cfg!(target_env = "ohos") {
653 match &error {
654 ParseFailure::Stderr(doc) => log::error!("{doc}"),
655 ParseFailure::Stdout(doc, _) => log::error!("{doc}"),
657 ParseFailure::Completion(_) => log::error!("Not supported on these platforms"),
658 }
659 } else {
660 error.print_message(80);
661 }
662
663 return if error.exit_code() == 0 {
664 ArgumentParsingResult::Exit
665 } else {
666 ArgumentParsingResult::ErrorParsing
667 };
668 },
669 };
670
671 if let Some(content_process) = cmd_args.content_process {
673 return ArgumentParsingResult::ContentProcess(content_process);
674 }
675
676 let config_dir = cmd_args
677 .config_dir
678 .clone()
679 .or_else(default_config_dir)
680 .inspect(|config_dir| {
681 if !config_dir.exists() {
682 fs::create_dir_all(config_dir).expect("Could not create config_dir");
683 }
684 });
685 let temporary_storage = cmd_args.temporary_storage;
686 if let Some(ref time_profiler_trace_path) = cmd_args.profiler_trace_path {
687 let mut path = PathBuf::from(time_profiler_trace_path);
688 path.pop();
689 fs::create_dir_all(&path).expect("Error in creating profiler trace path");
690 }
691
692 let mut preferences = get_preferences(&cmd_args.prefs_file, &config_dir);
693
694 update_preferences_from_command_line_arguments(&mut preferences, &cmd_args);
695
696 if cfg!(target_os = "android") && cfg!(target_pointer_width = "32") {
698 preferences.js_baseline_interpreter_enabled = false;
699 preferences.js_baseline_jit_enabled = false;
700 preferences.js_ion_enabled = false;
701 }
702
703 let default_window_size = Size2D::new(1024, 740);
705 let default_window_size = cmd_args
706 .screen_size_override
707 .map_or(default_window_size, |screen_size_override| {
708 default_window_size.min(screen_size_override)
709 });
710
711 let servoshell_preferences = ServoShellPreferences {
712 url: Some(cmd_args.url),
713 no_native_titlebar: cmd_args.no_native_titlebar,
714 device_pixel_ratio_override: cmd_args.device_pixel_ratio,
715 clean_shutdown: cmd_args.clean_shutdown,
716 headless: cmd_args.headless,
717 tracing_filter: cmd_args.tracing_filter,
718 initial_window_size: cmd_args.window_size.unwrap_or(default_window_size),
719 screen_size_override: cmd_args.screen_size_override,
720 simulate_touch_events: cmd_args.simulate_touch_events,
721 webdriver_port: Cell::new(cmd_args.webdriver_port),
722 output_image_path: cmd_args.output.map(|p| p.to_string_lossy().into_owned()),
723 exit_after_stable_image: cmd_args.exit,
724 userscripts_directory: cmd_args.userscripts,
725 user_stylesheets: cmd_args.user_stylesheet,
726 experimental_preferences_enabled: cmd_args.enable_experimental_web_platform_features,
727 #[cfg(target_env = "ohos")]
728 log_filter: cmd_args.log_filter.or_else(|| {
729 (!preferences.log_filter.is_empty()).then(|| preferences.log_filter.clone())
730 }),
731 #[cfg(target_env = "ohos")]
732 log_to_file: cmd_args.log_to_file,
733 ..Default::default()
734 };
735
736 let Ok(debug_options) = parse_diagnostics_logging(cmd_args.debug) else {
737 return ArgumentParsingResult::ErrorParsing;
738 };
739
740 let opts = Opts {
741 debug: debug_options,
742 time_profiling: cmd_args.profile,
743 time_profiler_trace_path: cmd_args
744 .profiler_trace_path
745 .map(|p| p.to_string_lossy().into_owned()),
746 hard_fail: cmd_args.hard_fail,
747 multiprocess: cmd_args.multiprocess,
748 background_hang_monitor: cmd_args.background_hang_monitor,
749 sandbox: cmd_args.sandbox,
750 random_pipeline_closure_probability: cmd_args.random_pipeline_closure_probability,
751 random_pipeline_closure_seed: cmd_args.random_pipeline_closure_seed,
752 config_dir,
753 temporary_storage,
754 shaders_path: cmd_args.shaders,
755 certificate_path: cmd_args
756 .certificate_path
757 .map(|p| p.to_string_lossy().into_owned()),
758 host_file: cmd_args.host_file,
759 ignore_certificate_errors: cmd_args.ignore_certificate_errors,
760 unminify_js: cmd_args.unminify_js,
761 local_script_source: cmd_args
762 .local_script_source
763 .map(|p| p.to_string_lossy().into_owned()),
764 unminify_css: cmd_args.unminify_css,
765 force_ipc: cmd_args.force_ipc,
766 };
767
768 ArgumentParsingResult::ChromeProcess(opts, preferences, servoshell_preferences)
769}
770
771fn parse_diagnostics_logging(cli_options: Vec<String>) -> Result<DiagnosticsLogging, ()> {
773 fn print_option(name: &str, description: &str) {
774 println!("\t{:<35} {}", name, description);
775 }
776
777 if cli_options.contains(&"help".into()) {
778 println!("Usage: servoshell -Z option,[option,...]\n\twhere options include:");
780 print_option("help", "Show this help message");
781 for option in DiagnosticsLoggingOption::iter() {
782 print_option(option.help_option(), option.help_message())
783 }
784
785 std::process::exit(0);
786 }
787
788 let mut diagnostics_logging = DiagnosticsLogging::new();
789 for cli_option in cli_options.iter() {
790 if let Err(error) = diagnostics_logging.extend_from_string(cli_option) {
791 eprintln!("Could not parse debug logging option: {error}");
792 return Err(());
793 }
794 }
795
796 Ok(diagnostics_logging)
797}
798
799#[cfg(test)]
800fn test_parse_pref(arg: &str) -> Preferences {
801 let args = ["--pref", arg];
802 match parse_command_line_arguments(args.as_slice()) {
803 ArgumentParsingResult::ContentProcess(..) => {
804 unreachable!("No preferences for content process")
805 },
806 ArgumentParsingResult::ChromeProcess(_, preferences, _) => preferences,
807 ArgumentParsingResult::Exit => {
808 panic!("we supplied a --pref argument above which should be parsed")
809 },
810 ArgumentParsingResult::ErrorParsing => {
811 unreachable!("we supplied a --pref argument above which should be parsed")
812 },
813 }
814}
815
816#[test]
817fn test_parse_pref_from_command_line() {
818 let preferences = test_parse_pref("dom_bluetooth_enabled=true");
820 assert!(preferences.dom_bluetooth_enabled);
821
822 let preferences = test_parse_pref("dom_bluetooth_enabled=false");
823 assert!(!preferences.dom_bluetooth_enabled);
824
825 let preferences = test_parse_pref("layout_threads=42");
827 assert_eq!(preferences.layout_threads, 42);
828
829 let preferences = test_parse_pref("network_http_cache_size=50");
831 assert_eq!(preferences.network_http_cache_size, 50);
832 let preferences = test_parse_pref("network_connection_timeout=30");
833 assert_eq!(preferences.network_connection_timeout, 30);
834
835 let preferences = test_parse_pref("fonts_default=Lucida");
837 assert_eq!(preferences.fonts_default, "Lucida");
838
839 let preferences = test_parse_pref("dom_bluetooth_enabled");
841 assert!(preferences.dom_bluetooth_enabled);
842}
843
844#[test]
845fn test_invalid_prefs_from_command_line_panics() {
846 let err_msg = std::panic::catch_unwind(|| {
847 test_parse_pref("doesntexist=true");
848 })
849 .err()
850 .and_then(|a| a.downcast_ref::<String>().cloned())
851 .expect("Should panic");
852 assert_eq!(
853 err_msg, "Unknown preference: \"doesntexist\"",
854 "Message should describe the problem"
855 )
856}
857
858#[test]
859fn test_create_prefs_map() {
860 let json_str = "{
861 \"layout.writing-mode.enabled\": true,
862 \"network.mime.sniff\": false,
863 \"shell.homepage\": \"https://servo.org\"
864 }";
865 assert_eq!(read_prefs_map(json_str).len(), 3);
866}
867
868#[cfg(test)]
869fn test_parse(arg: &str) -> (Opts, Preferences, ServoShellPreferences) {
870 let args_split: Vec<&str> = arg.split_whitespace().collect();
872 match parse_command_line_arguments(args_split.as_slice()) {
873 ArgumentParsingResult::ContentProcess(..) => {
874 unreachable!("No preferences for content process")
875 },
876 ArgumentParsingResult::ChromeProcess(opts, preferences, servoshell_preferences) => {
877 (opts, preferences, servoshell_preferences)
878 },
879 ArgumentParsingResult::Exit | ArgumentParsingResult::ErrorParsing => {
880 unreachable!("We always have valid preference in our test cases")
881 },
882 }
883}
884
885#[test]
886fn test_profiling_args() {
887 assert_eq!(
888 test_parse("-p").0.time_profiling.unwrap(),
889 OutputOptions::Stdout(5_f64)
890 );
891
892 assert_eq!(
893 test_parse("-p 10").0.time_profiling.unwrap(),
894 OutputOptions::Stdout(10_f64)
895 );
896
897 assert_eq!(
898 test_parse("-p 10.0").0.time_profiling.unwrap(),
899 OutputOptions::Stdout(10_f64)
900 );
901
902 assert_eq!(
903 test_parse("-p foo.txt").0.time_profiling.unwrap(),
904 OutputOptions::FileName(String::from("foo.txt"))
905 );
906}
907
908#[test]
909fn test_servoshell_cmd() {
910 assert_eq!(
911 test_parse("--screen-size=1000x1000")
912 .2
913 .screen_size_override
914 .unwrap(),
915 Size2D::new(1000, 1000)
916 );
917
918 assert_eq!(
919 test_parse("--certificate-path=/tmp/test")
920 .0
921 .certificate_path
922 .unwrap(),
923 String::from("/tmp/test")
924 );
925
926 assert!({
927 let p = test_parse("--zealous-gc").1;
928 p.js_mem_gc_zeal_level == 2 && p.js_mem_gc_zeal_frequency == 1
929 });
930}