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 //! Configuration options for a single run of the servo application. Created
6 //! from command line arguments.
7 
8 use euclid::TypedSize2D;
9 use getopts::Options;
10 use num_cpus;
11 use prefs::{self, PrefValue, PREFS};
12 use resource_files::set_resources_path;
13 use servo_geometry::DeviceIndependentPixel;
14 use servo_url::ServoUrl;
15 use std::borrow::Cow;
16 use std::cmp;
17 use std::default::Default;
18 use std::env;
19 use std::fs::{self, File};
20 use std::io::{self, Read, Write};
21 use std::path::{Path, PathBuf};
22 use std::process;
23 use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering};
24 use url::{self, Url};
25 
26 
27 /// Global flags for Servo, currently set on the command line.
28 #[derive(Clone, Deserialize, Serialize)]
29 pub struct Opts {
30     pub is_running_problem_test: bool,
31 
32     /// The initial URL to load.
33     pub url: Option<ServoUrl>,
34 
35     /// The maximum size of each tile in pixels (`-s`).
36     pub tile_size: usize,
37 
38     /// The ratio of device pixels per px at the default scale. If unspecified, will use the
39     /// platform default setting.
40     pub device_pixels_per_px: Option<f32>,
41 
42     /// `None` to disable the time profiler or `Some` to enable it with:
43     ///
44     ///  - an interval in seconds to cause it to produce output on that interval.
45     ///    (`i.e. -p 5`).
46     ///  - a file path to write profiling info to a TSV file upon Servo's termination.
47     ///    (`i.e. -p out.tsv`).
48     ///  - an InfluxDB hostname to store profiling info upon Servo's termination.
49     ///    (`i.e. -p http://localhost:8086`)
50     pub time_profiling: Option<OutputOptions>,
51 
52     /// When the profiler is enabled, this is an optional path to dump a self-contained HTML file
53     /// visualizing the traces as a timeline.
54     pub time_profiler_trace_path: Option<String>,
55 
56     /// `None` to disable the memory profiler or `Some` with an interval in seconds to enable it
57     /// and cause it to produce output on that interval (`-m`).
58     pub mem_profiler_period: Option<f64>,
59 
60     pub nonincremental_layout: bool,
61 
62     /// Where to load userscripts from, if any. An empty string will load from
63     /// the resources/user-agent-js directory, and if the option isn't passed userscripts
64     /// won't be loaded
65     pub userscripts: Option<String>,
66 
67     pub user_stylesheets: Vec<(Vec<u8>, ServoUrl)>,
68 
69     pub output_file: Option<String>,
70 
71     /// Replace unpaires surrogates in DOM strings with U+FFFD.
72     /// See <https://github.com/servo/servo/issues/6564>
73     pub replace_surrogates: bool,
74 
75     /// Log GC passes and their durations.
76     pub gc_profile: bool,
77 
78     /// Load web fonts synchronously to avoid non-deterministic network-driven reflows.
79     pub load_webfonts_synchronously: bool,
80 
81     pub headless: bool,
82     pub hard_fail: bool,
83 
84     /// True if we should bubble intrinsic widths sequentially (`-b`). If this is true, then
85     /// intrinsic widths are computed as a separate pass instead of during flow construction. You
86     /// may wish to turn this flag on in order to benchmark style recalculation against other
87     /// browser engines.
88     pub bubble_inline_sizes_separately: bool,
89 
90     /// True if we should show borders on all fragments for debugging purposes
91     /// (`--show-debug-fragment-borders`).
92     pub show_debug_fragment_borders: bool,
93 
94     /// True if we should paint borders around flows based on which thread painted them.
95     pub show_debug_parallel_layout: bool,
96 
97     /// If set with --disable-text-aa, disable antialiasing on fonts. This is primarily useful for reftests
98     /// where pixel perfect results are required when using fonts such as the Ahem
99     /// font for layout tests.
100     pub enable_text_antialiasing: bool,
101 
102     /// If set with --disable-subpixel, use subpixel antialiasing for glyphs. In the future
103     /// this will likely become the default, but for now it's opt-in while we work
104     /// out any bugs and improve the implementation.
105     pub enable_subpixel_text_antialiasing: bool,
106 
107     /// If set with --disable-canvas-aa, disable antialiasing on the HTML canvas element.
108     /// Like --disable-text-aa, this is useful for reftests where pixel perfect results are required.
109     pub enable_canvas_antialiasing: bool,
110 
111     /// True if each step of layout is traced to an external JSON file
112     /// for debugging purposes. Settings this implies sequential layout
113     /// and paint.
114     pub trace_layout: bool,
115 
116     /// Periodically print out on which events script threads spend their processing time.
117     pub profile_script_events: bool,
118 
119     /// Enable all heartbeats for profiling.
120     pub profile_heartbeats: bool,
121 
122     /// `None` to disable debugger or `Some` with a port number to start a server to listen to
123     /// remote Firefox debugger connections.
124     pub debugger_port: Option<u16>,
125 
126     /// `None` to disable devtools or `Some` with a port number to start a server to listen to
127     /// remote Firefox devtools connections.
128     pub devtools_port: Option<u16>,
129 
130     /// `None` to disable WebDriver or `Some` with a port number to start a server to listen to
131     /// remote WebDriver commands.
132     pub webdriver_port: Option<u16>,
133 
134     /// The initial requested size of the window.
135     pub initial_window_size: TypedSize2D<u32, DeviceIndependentPixel>,
136 
137     /// An optional string allowing the user agent to be set for testing.
138     pub user_agent: Cow<'static, str>,
139 
140     /// Whether we're running in multiprocess mode.
141     pub multiprocess: bool,
142 
143     /// Whether we're running inside the sandbox.
144     pub sandbox: bool,
145 
146     /// Probability of randomly closing a pipeline,
147     /// used for testing the hardening of the constellation.
148     pub random_pipeline_closure_probability: Option<f32>,
149 
150     /// The seed for the RNG used to randomly close pipelines,
151     /// used for testing the hardening of the constellation.
152     pub random_pipeline_closure_seed: Option<usize>,
153 
154     /// Dumps the DOM after restyle.
155     pub dump_style_tree: bool,
156 
157     /// Dumps the rule tree.
158     pub dump_rule_tree: bool,
159 
160     /// Dumps the flow tree after a layout.
161     pub dump_flow_tree: bool,
162 
163     /// Dumps the display list after a layout.
164     pub dump_display_list: bool,
165 
166     /// Dumps the display list in JSON form after a layout.
167     pub dump_display_list_json: bool,
168 
169     /// Emits notifications when there is a relayout.
170     pub relayout_event: bool,
171 
172     /// Whether Style Sharing Cache is used
173     pub disable_share_style_cache: bool,
174 
175     /// Whether to show in stdout style sharing cache stats after a restyle.
176     pub style_sharing_stats: bool,
177 
178     /// Translate mouse input into touch events.
179     pub convert_mouse_to_touch: bool,
180 
181     /// True to exit after the page load (`-x`).
182     pub exit_after_load: bool,
183 
184     /// Do not use native titlebar
185     pub no_native_titlebar: bool,
186 
187     /// Enable vsync in the compositor
188     pub enable_vsync: bool,
189 
190     /// True to show webrender profiling stats on screen.
191     pub webrender_stats: bool,
192 
193     /// True to show webrender debug on screen.
194     pub webrender_debug: bool,
195 
196     /// True if webrender recording should be enabled.
197     pub webrender_record: bool,
198 
199     /// True if webrender is allowed to batch draw calls as instances.
200     pub webrender_batch: bool,
201 
202     /// True to compile all webrender shaders at init time. This is mostly
203     /// useful when modifying the shaders, to ensure they all compile
204     /// after each change is made.
205     pub precache_shaders: bool,
206 
207     /// True if WebRender should use multisample antialiasing.
208     pub use_msaa: bool,
209 
210     /// Directory for a default config directory
211     pub config_dir: Option<PathBuf>,
212 
213     // don't skip any backtraces on panic
214     pub full_backtraces: bool,
215 
216     /// True to use OS native signposting facilities. This makes profiling events (script activity,
217     /// reflow, compositing, etc.) appear in Instruments.app on macOS.
218     pub signpost: bool,
219 
220     /// Print the version and exit.
221     pub is_printing_version: bool,
222 
223     /// Path to SSL certificates.
224     pub certificate_path: Option<String>,
225 
226     /// Unminify Javascript.
227     pub unminify_js: bool,
228 
229     /// Print Progressive Web Metrics to console.
230     pub print_pwm: bool,
231 }
232 
print_usage(app: &str, opts: &Options)233 fn print_usage(app: &str, opts: &Options) {
234     let message = format!("Usage: {} [ options ... ] [URL]\n\twhere options include", app);
235     println!("{}", opts.usage(&message));
236 }
237 
238 
239 /// Debug options for Servo, currently set on the command line with -Z
240 #[derive(Default)]
241 pub struct DebugOptions {
242     /// List all the debug options.
243     pub help: bool,
244 
245     /// Bubble intrinsic widths separately like other engines.
246     pub bubble_widths: bool,
247 
248     /// Disable antialiasing of rendered text.
249     pub disable_text_aa: bool,
250 
251     /// Disable subpixel antialiasing of rendered text.
252     pub disable_subpixel_aa: bool,
253 
254     /// Disable antialiasing of rendered text on the HTML canvas element.
255     pub disable_canvas_aa: bool,
256 
257     /// Print the DOM after each restyle.
258     pub dump_style_tree: bool,
259 
260     /// Dumps the rule tree.
261     pub dump_rule_tree: bool,
262 
263     /// Print the flow tree after each layout.
264     pub dump_flow_tree: bool,
265 
266     /// Print the display list after each layout.
267     pub dump_display_list: bool,
268 
269     /// Print the display list in JSON form.
270     pub dump_display_list_json: bool,
271 
272     /// Print notifications when there is a relayout.
273     pub relayout_event: bool,
274 
275     /// Profile which events script threads spend their time on.
276     pub profile_script_events: bool,
277 
278     /// Enable all heartbeats for profiling.
279     pub profile_heartbeats: bool,
280 
281     /// Paint borders along fragment boundaries.
282     pub show_fragment_borders: bool,
283 
284     /// Mark which thread laid each flow out with colors.
285     pub show_parallel_layout: bool,
286 
287     /// Write layout trace to an external file for debugging.
288     pub trace_layout: bool,
289 
290     /// Disable the style sharing cache.
291     pub disable_share_style_cache: bool,
292 
293     /// Whether to show in stdout style sharing cache stats after a restyle.
294     pub style_sharing_stats: bool,
295 
296     /// Translate mouse input into touch events.
297     pub convert_mouse_to_touch: bool,
298 
299     /// Replace unpaires surrogates in DOM strings with U+FFFD.
300     /// See <https://github.com/servo/servo/issues/6564>
301     pub replace_surrogates: bool,
302 
303     /// Log GC passes and their durations.
304     pub gc_profile: bool,
305 
306     /// Load web fonts synchronously to avoid non-deterministic network-driven reflows.
307     pub load_webfonts_synchronously: bool,
308 
309     /// Disable vsync in the compositor
310     pub disable_vsync: bool,
311 
312     /// Show webrender profiling stats on screen.
313     pub webrender_stats: bool,
314 
315     /// Show webrender debug on screen.
316     pub webrender_debug: bool,
317 
318     /// Enable webrender recording.
319     pub webrender_record: bool,
320 
321     /// Enable webrender instanced draw call batching.
322     pub webrender_disable_batch: bool,
323 
324     /// Use multisample antialiasing in WebRender.
325     pub use_msaa: bool,
326 
327     // don't skip any backtraces on panic
328     pub full_backtraces: bool,
329 
330     /// True to compile all webrender shaders at init time. This is mostly
331     /// useful when modifying the shaders, to ensure they all compile
332     /// after each change is made.
333     pub precache_shaders: bool,
334 
335     /// True to use OS native signposting facilities. This makes profiling events (script activity,
336     /// reflow, compositing, etc.) appear in Instruments.app on macOS.
337     pub signpost: bool,
338 }
339 
340 
341 impl DebugOptions {
extend(&mut self, debug_string: String) -> Result<(), String>342     pub fn extend(&mut self, debug_string: String) -> Result<(), String> {
343         for option in debug_string.split(',') {
344             match option {
345                 "help" => self.help = true,
346                 "bubble-widths" => self.bubble_widths = true,
347                 "disable-text-aa" => self.disable_text_aa = true,
348                 "disable-subpixel-aa" => self.disable_subpixel_aa = true,
349                 "disable-canvas-aa" => self.disable_text_aa = true,
350                 "dump-style-tree" => self.dump_style_tree = true,
351                 "dump-rule-tree" => self.dump_rule_tree = true,
352                 "dump-flow-tree" => self.dump_flow_tree = true,
353                 "dump-display-list" => self.dump_display_list = true,
354                 "dump-display-list-json" => self.dump_display_list_json = true,
355                 "relayout-event" => self.relayout_event = true,
356                 "profile-script-events" => self.profile_script_events = true,
357                 "profile-heartbeats" => self.profile_heartbeats = true,
358                 "show-fragment-borders" => self.show_fragment_borders = true,
359                 "show-parallel-layout" => self.show_parallel_layout = true,
360                 "trace-layout" => self.trace_layout = true,
361                 "disable-share-style-cache" => self.disable_share_style_cache = true,
362                 "style-sharing-stats" => self.style_sharing_stats = true,
363                 "convert-mouse-to-touch" => self.convert_mouse_to_touch = true,
364                 "replace-surrogates" => self.replace_surrogates = true,
365                 "gc-profile" => self.gc_profile = true,
366                 "load-webfonts-synchronously" => self.load_webfonts_synchronously = true,
367                 "disable-vsync" => self.disable_vsync = true,
368                 "wr-stats" => self.webrender_stats = true,
369                 "wr-debug" => self.webrender_debug = true,
370                 "wr-record" => self.webrender_record = true,
371                 "wr-no-batch" => self.webrender_disable_batch = true,
372                 "msaa" => self.use_msaa = true,
373                 "full-backtraces" => self.full_backtraces = true,
374                 "precache-shaders" => self.precache_shaders = true,
375                 "signpost" => self.signpost = true,
376                 "" => {},
377                 _ => return Err(String::from(option)),
378             };
379         };
380         Ok(())
381     }
382 }
383 
384 
print_debug_usage(app: &str) -> !385 fn print_debug_usage(app: &str) -> ! {
386     fn print_option(name: &str, description: &str) {
387         println!("\t{:<35} {}", name, description);
388     }
389 
390     println!("Usage: {} debug option,[options,...]\n\twhere options include\n\nOptions:", app);
391 
392     print_option("bubble-widths", "Bubble intrinsic widths separately like other engines.");
393     print_option("disable-text-aa", "Disable antialiasing of rendered text.");
394     print_option("disable-canvas-aa", "Disable antialiasing on the HTML canvas element.");
395     print_option("dump-style-tree", "Print the DOM with computed styles after each restyle.");
396     print_option("dump-flow-tree", "Print the flow tree after each layout.");
397     print_option("dump-display-list", "Print the display list after each layout.");
398     print_option("dump-display-list-json", "Print the display list in JSON form.");
399     print_option("relayout-event", "Print notifications when there is a relayout.");
400     print_option("profile-script-events", "Enable profiling of script-related events.");
401     print_option("profile-heartbeats", "Enable heartbeats for all thread categories.");
402     print_option("show-fragment-borders", "Paint borders along fragment boundaries.");
403     print_option("show-parallel-layout", "Mark which thread laid each flow out with colors.");
404     print_option("trace-layout", "Write layout trace to an external file for debugging.");
405     print_option("disable-share-style-cache",
406                  "Disable the style sharing cache.");
407     print_option("parallel-display-list-building", "Build display lists in parallel.");
408     print_option("convert-mouse-to-touch", "Send touch events instead of mouse events");
409     print_option("replace-surrogates", "Replace unpaires surrogates in DOM strings with U+FFFD. \
410                                         See https://github.com/servo/servo/issues/6564");
411     print_option("gc-profile", "Log GC passes and their durations.");
412     print_option("load-webfonts-synchronously",
413                  "Load web fonts synchronously to avoid non-deterministic network-driven reflows");
414     print_option("disable-vsync",
415                  "Disable vsync mode in the compositor to allow profiling at more than monitor refresh rate");
416     print_option("wr-stats", "Show WebRender profiler on screen.");
417     print_option("msaa", "Use multisample antialiasing in WebRender.");
418     print_option("full-backtraces", "Print full backtraces for all errors");
419     print_option("wr-debug", "Display webrender tile borders.");
420     print_option("wr-no-batch", "Disable webrender instanced batching.");
421     print_option("precache-shaders", "Compile all shaders during init.");
422     print_option("signpost", "Emit native OS signposts for profile events (currently macOS only)");
423 
424     println!("");
425 
426     process::exit(0)
427 }
428 
429 #[derive(Clone, Deserialize, Serialize)]
430 pub enum OutputOptions {
431     /// Database connection config (hostname, name, user, pass)
432     DB(ServoUrl, Option<String>, Option<String>, Option<String>),
433     FileName(String),
434     Stdout(f64),
435 }
436 
args_fail(msg: &str) -> !437 fn args_fail(msg: &str) -> ! {
438     writeln!(io::stderr(), "{}", msg).unwrap();
439     process::exit(1)
440 }
441 
442 static MULTIPROCESS: AtomicBool = ATOMIC_BOOL_INIT;
443 
444 #[inline]
multiprocess() -> bool445 pub fn multiprocess() -> bool {
446     MULTIPROCESS.load(Ordering::Relaxed)
447 }
448 
449 enum UserAgent {
450     Desktop,
451     Android,
452     #[allow(non_camel_case_types)]
453     iOS
454 }
455 
default_user_agent_string(agent: UserAgent) -> &'static str456 fn default_user_agent_string(agent: UserAgent) -> &'static str {
457     #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
458     const DESKTOP_UA_STRING: &'static str =
459         "Mozilla/5.0 (X11; Linux x86_64; rv:55.0) Servo/1.0 Firefox/55.0";
460     #[cfg(all(target_os = "linux", not(target_arch = "x86_64")))]
461     const DESKTOP_UA_STRING: &'static str =
462         "Mozilla/5.0 (X11; Linux i686; rv:55.0) Servo/1.0 Firefox/55.0";
463 
464     #[cfg(all(target_os = "windows", target_arch = "x86_64"))]
465     const DESKTOP_UA_STRING: &'static str =
466         "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:55.0) Servo/1.0 Firefox/55.0";
467     #[cfg(all(target_os = "windows", not(target_arch = "x86_64")))]
468     const DESKTOP_UA_STRING: &'static str =
469         "Mozilla/5.0 (Windows NT 6.1; rv:55.0) Servo/1.0 Firefox/55.0";
470 
471     #[cfg(not(any(target_os = "linux", target_os = "windows")))]
472     // Neither Linux nor Windows, so maybe OS X, and if not then OS X is an okay fallback.
473     const DESKTOP_UA_STRING: &'static str =
474         "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:55.0) Servo/1.0 Firefox/55.0";
475 
476 
477     match agent {
478         UserAgent::Desktop => {
479             DESKTOP_UA_STRING
480         }
481         UserAgent::Android => {
482             "Mozilla/5.0 (Android; Mobile; rv:55.0) Servo/1.0 Firefox/55.0"
483         }
484         UserAgent::iOS => {
485             "Mozilla/5.0 (iPhone; CPU iPhone OS 8_3 like Mac OS X; rv:55.0) Servo/1.0 Firefox/55.0"
486         }
487     }
488 }
489 
490 #[cfg(target_os = "android")]
491 const DEFAULT_USER_AGENT: UserAgent = UserAgent::Android;
492 
493 #[cfg(target_os = "ios")]
494 const DEFAULT_USER_AGENT: UserAgent = UserAgent::iOS;
495 
496 #[cfg(not(any(target_os = "android", target_os = "ios")))]
497 const DEFAULT_USER_AGENT: UserAgent = UserAgent::Desktop;
498 
default_opts() -> Opts499 pub fn default_opts() -> Opts {
500     Opts {
501         is_running_problem_test: false,
502         url: None,
503         tile_size: 512,
504         device_pixels_per_px: None,
505         time_profiling: None,
506         time_profiler_trace_path: None,
507         mem_profiler_period: None,
508         nonincremental_layout: false,
509         userscripts: None,
510         user_stylesheets: Vec::new(),
511         output_file: None,
512         replace_surrogates: false,
513         gc_profile: false,
514         load_webfonts_synchronously: false,
515         headless: false,
516         hard_fail: true,
517         bubble_inline_sizes_separately: false,
518         show_debug_fragment_borders: false,
519         show_debug_parallel_layout: false,
520         enable_text_antialiasing: true,
521         enable_subpixel_text_antialiasing: true,
522         enable_canvas_antialiasing: true,
523         trace_layout: false,
524         debugger_port: None,
525         devtools_port: None,
526         webdriver_port: None,
527         initial_window_size: TypedSize2D::new(1024, 740),
528         user_agent: default_user_agent_string(DEFAULT_USER_AGENT).into(),
529         multiprocess: false,
530         random_pipeline_closure_probability: None,
531         random_pipeline_closure_seed: None,
532         sandbox: false,
533         dump_style_tree: false,
534         dump_rule_tree: false,
535         dump_flow_tree: false,
536         dump_display_list: false,
537         dump_display_list_json: false,
538         relayout_event: false,
539         profile_script_events: false,
540         profile_heartbeats: false,
541         disable_share_style_cache: false,
542         style_sharing_stats: false,
543         convert_mouse_to_touch: false,
544         exit_after_load: false,
545         no_native_titlebar: false,
546         enable_vsync: true,
547         webrender_stats: false,
548         use_msaa: false,
549         config_dir: None,
550         full_backtraces: false,
551         is_printing_version: false,
552         webrender_debug: false,
553         webrender_record: false,
554         webrender_batch: true,
555         precache_shaders: false,
556         signpost: false,
557         certificate_path: None,
558         unminify_js: false,
559         print_pwm: false,
560     }
561 }
562 
from_cmdline_args(args: &[String]) -> ArgumentParsingResult563 pub fn from_cmdline_args(args: &[String]) -> ArgumentParsingResult {
564     let (app_name, args) = args.split_first().unwrap();
565 
566     let mut opts = Options::new();
567     opts.optflag("c", "cpu", "CPU painting");
568     opts.optflag("g", "gpu", "GPU painting");
569     opts.optopt("o", "output", "Output file", "output.png");
570     opts.optopt("s", "size", "Size of tiles", "512");
571     opts.optopt("", "device-pixel-ratio", "Device pixels per px", "");
572     opts.optflagopt("p", "profile", "Time profiler flag and either a TSV output filename \
573         OR an interval for output to Stdout (blank for Stdout with interval of 5s)", "10 \
574         OR time.tsv");
575     opts.optflagopt("", "profiler-trace-path",
576                     "Path to dump a self-contained HTML timeline of profiler traces",
577                     "");
578     opts.optflagopt("m", "memory-profile", "Memory profiler flag and output interval", "10");
579     opts.optflag("x", "exit", "Exit after load flag");
580     opts.optopt("y", "layout-threads", "Number of threads to use for layout", "1");
581     opts.optflag("i", "nonincremental-layout", "Enable to turn off incremental layout.");
582     opts.optflagopt("", "userscripts",
583                     "Uses userscripts in resources/user-agent-js, or a specified full path", "");
584     opts.optmulti("", "user-stylesheet",
585                   "A user stylesheet to be added to every document", "file.css");
586     opts.optflag("z", "headless", "Headless mode");
587     opts.optflag("f", "hard-fail", "Exit on thread failure instead of displaying about:failure");
588     opts.optflag("F", "soft-fail", "Display about:failure on thread failure instead of exiting");
589     opts.optflagopt("", "remote-debugging-port", "Start remote debugger server on port", "2794");
590     opts.optflagopt("", "devtools", "Start remote devtools server on port", "6000");
591     opts.optflagopt("", "webdriver", "Start remote WebDriver server on port", "7000");
592     opts.optopt("", "resolution", "Set window resolution.", "1024x740");
593     opts.optopt("u",
594                 "user-agent",
595                 "Set custom user agent string (or ios / android / desktop for platform default)",
596                 "NCSA Mosaic/1.0 (X11;SunOS 4.1.4 sun4m)");
597     opts.optflag("M", "multiprocess", "Run in multiprocess mode");
598     opts.optflag("S", "sandbox", "Run in a sandbox if multiprocess");
599     opts.optopt("",
600                 "random-pipeline-closure-probability",
601                 "Probability of randomly closing a pipeline (for testing constellation hardening).",
602                 "0.0");
603     opts.optopt("", "random-pipeline-closure-seed", "A fixed seed for repeatbility of random pipeline closure.", "");
604     opts.optmulti("Z", "debug",
605                   "A comma-separated string of debug options. Pass help to show available options.", "");
606     opts.optflag("h", "help", "Print this message");
607     opts.optopt("", "resources-path", "Path to find static resources", "/home/servo/resources");
608     opts.optopt("", "certificate-path", "Path to find SSL certificates", "/home/servo/resources/certs");
609     opts.optopt("", "content-process" , "Run as a content process and connect to the given pipe",
610                 "servo-ipc-channel.abcdefg");
611     opts.optmulti("", "pref",
612                   "A preference to set to enable", "dom.bluetooth.enabled");
613     opts.optflag("b", "no-native-titlebar", "Do not use native titlebar");
614     opts.optflag("w", "webrender", "Use webrender backend");
615     opts.optopt("G", "graphics", "Select graphics backend (gl or es2)", "gl");
616     opts.optopt("", "config-dir",
617                     "config directory following xdg spec on linux platform", "");
618     opts.optflag("v", "version", "Display servo version information");
619     opts.optflag("", "unminify-js", "Unminify Javascript");
620     opts.optopt("", "profiler-db-user", "Profiler database user", "");
621     opts.optopt("", "profiler-db-pass", "Profiler database password", "");
622     opts.optopt("", "profiler-db-name", "Profiler database name", "");
623     opts.optflag("", "print-pwm", "Print Progressive Web Metrics");
624 
625     let opt_match = match opts.parse(args) {
626         Ok(m) => m,
627         Err(f) => args_fail(&f.to_string()),
628     };
629 
630     set_resources_path(opt_match.opt_str("resources-path"));
631 
632     if opt_match.opt_present("h") || opt_match.opt_present("help") {
633         print_usage(app_name, &opts);
634         process::exit(0);
635     };
636 
637     // If this is the content process, we'll receive the real options over IPC. So just fill in
638     // some dummy options for now.
639     if let Some(content_process) = opt_match.opt_str("content-process") {
640         MULTIPROCESS.store(true, Ordering::SeqCst);
641         return ArgumentParsingResult::ContentProcess(content_process);
642     }
643 
644     let mut debug_options = DebugOptions::default();
645 
646     for debug_string in opt_match.opt_strs("Z") {
647         if let Err(e) = debug_options.extend(debug_string) {
648             args_fail(&format!("error: unrecognized debug option: {}", e));
649         }
650     }
651 
652     if debug_options.help {
653         print_debug_usage(app_name)
654     }
655 
656     let cwd = env::current_dir().unwrap();
657     let url_opt = if !opt_match.free.is_empty() {
658         Some(&opt_match.free[0][..])
659     } else {
660         None
661     };
662     let is_running_problem_test =
663         url_opt
664         .as_ref()
665         .map_or(false, |url|
666              url.starts_with("http://web-platform.test:8000/2dcontext/drawing-images-to-the-canvas/") ||
667              url.starts_with("http://web-platform.test:8000/_mozilla/mozilla/canvas/") ||
668              url.starts_with("http://web-platform.test:8000/_mozilla/css/canvas_over_area.html"));
669 
670     let url_opt = url_opt.and_then(|url_string| parse_url_or_filename(&cwd, url_string)
671                                    .or_else(|error| {
672                                        warn!("URL parsing failed ({:?}).", error);
673                                        Err(error)
674                                    }).ok());
675 
676     let tile_size: usize = match opt_match.opt_str("s") {
677         Some(tile_size_str) => tile_size_str.parse()
678             .unwrap_or_else(|err| args_fail(&format!("Error parsing option: -s ({})", err))),
679         None => 512,
680     };
681 
682     let device_pixels_per_px = opt_match.opt_str("device-pixel-ratio").map(|dppx_str|
683         dppx_str.parse()
684             .unwrap_or_else(|err| args_fail(&format!("Error parsing option: --device-pixel-ratio ({})", err)))
685     );
686 
687     // If only the flag is present, default to a 5 second period for both profilers
688     let time_profiling = if opt_match.opt_present("p") {
689         match opt_match.opt_str("p") {
690             Some(argument) => match argument.parse::<f64>() {
691                 Ok(interval) => Some(OutputOptions::Stdout(interval)) ,
692                 Err(_) => {
693                     match ServoUrl::parse(&argument) {
694                         Ok(url) => Some(OutputOptions::DB(url, opt_match.opt_str("profiler-db-name"),
695                                                           opt_match.opt_str("profiler-db-user"),
696                                                           opt_match.opt_str("profiler-db-pass"))),
697                         Err(_) => Some(OutputOptions::FileName(argument)),
698                     }
699                 }
700             },
701             None => Some(OutputOptions::Stdout(5.0 as f64)),
702         }
703     } else {
704         // if the p option doesn't exist:
705         None
706     };
707 
708     if let Some(ref time_profiler_trace_path) = opt_match.opt_str("profiler-trace-path") {
709         let mut path = PathBuf::from(time_profiler_trace_path);
710         path.pop();
711         if let Err(why) = fs::create_dir_all(&path) {
712             error!("Couldn't create/open {:?}: {:?}",
713                 Path::new(time_profiler_trace_path).to_string_lossy(), why);
714         }
715     }
716 
717     let mem_profiler_period = opt_match.opt_default("m", "5").map(|period| {
718         period.parse().unwrap_or_else(|err| args_fail(&format!("Error parsing option: -m ({})", err)))
719     });
720 
721     let mut layout_threads: Option<usize> = opt_match.opt_str("y")
722         .map(|layout_threads_str| {
723             layout_threads_str.parse()
724                 .unwrap_or_else(|err| args_fail(&format!("Error parsing option: -y ({})", err)))
725         });
726 
727     let nonincremental_layout = opt_match.opt_present("i");
728 
729     let random_pipeline_closure_probability = opt_match.opt_str("random-pipeline-closure-probability").map(|prob|
730         prob.parse().unwrap_or_else(|err| {
731             args_fail(&format!("Error parsing option: --random-pipeline-closure-probability ({})", err))
732         })
733     );
734 
735     let random_pipeline_closure_seed = opt_match.opt_str("random-pipeline-closure-seed").map(|seed|
736         seed.parse().unwrap_or_else(|err| {
737             args_fail(&format!("Error parsing option: --random-pipeline-closure-seed ({})", err))
738         })
739     );
740 
741     let mut bubble_inline_sizes_separately = debug_options.bubble_widths;
742     if debug_options.trace_layout {
743         layout_threads = Some(1);
744         bubble_inline_sizes_separately = true;
745     }
746 
747     let debugger_port = opt_match.opt_default("remote-debugging-port", "2794").map(|port| {
748         port.parse()
749             .unwrap_or_else(|err| args_fail(&format!("Error parsing option: --remote-debugging-port ({})", err)))
750     });
751 
752     let devtools_port = opt_match.opt_default("devtools", "6000").map(|port| {
753         port.parse().unwrap_or_else(|err| args_fail(&format!("Error parsing option: --devtools ({})", err)))
754     });
755 
756     let webdriver_port = opt_match.opt_default("webdriver", "7000").map(|port| {
757         port.parse().unwrap_or_else(|err| args_fail(&format!("Error parsing option: --webdriver ({})", err)))
758     });
759 
760     let initial_window_size = match opt_match.opt_str("resolution") {
761         Some(res_string) => {
762             let res: Vec<u32> = res_string.split('x').map(|r| {
763                 r.parse().unwrap_or_else(|err| args_fail(&format!("Error parsing option: --resolution ({})", err)))
764             }).collect();
765             TypedSize2D::new(res[0], res[1])
766         }
767         None => {
768             TypedSize2D::new(1024, 740)
769         }
770     };
771 
772     if opt_match.opt_present("M") {
773         MULTIPROCESS.store(true, Ordering::SeqCst)
774     }
775 
776     let user_agent = match opt_match.opt_str("u") {
777         Some(ref ua) if ua == "ios" => default_user_agent_string(UserAgent::iOS).into(),
778         Some(ref ua) if ua == "android" => default_user_agent_string(UserAgent::Android).into(),
779         Some(ref ua) if ua == "desktop" => default_user_agent_string(UserAgent::Desktop).into(),
780         Some(ua) => ua.into(),
781         None => default_user_agent_string(DEFAULT_USER_AGENT).into(),
782     };
783 
784     let user_stylesheets = opt_match.opt_strs("user-stylesheet").iter().map(|filename| {
785         let path = cwd.join(filename);
786         let url = ServoUrl::from_url(Url::from_file_path(&path).unwrap());
787         let mut contents = Vec::new();
788         File::open(path)
789             .unwrap_or_else(|err| args_fail(&format!("Couldn't open {}: {}", filename, err)))
790             .read_to_end(&mut contents)
791             .unwrap_or_else(|err| args_fail(&format!("Couldn't read {}: {}", filename, err)));
792         (contents, url)
793     }).collect();
794 
795     let do_not_use_native_titlebar =
796         opt_match.opt_present("b") ||
797         !PREFS.get("shell.native-titlebar.enabled").as_boolean().unwrap();
798 
799     let is_printing_version = opt_match.opt_present("v") || opt_match.opt_present("version");
800 
801     let opts = Opts {
802         is_running_problem_test: is_running_problem_test,
803         url: url_opt,
804         tile_size: tile_size,
805         device_pixels_per_px: device_pixels_per_px,
806         time_profiling: time_profiling,
807         time_profiler_trace_path: opt_match.opt_str("profiler-trace-path"),
808         mem_profiler_period: mem_profiler_period,
809         nonincremental_layout: nonincremental_layout,
810         userscripts: opt_match.opt_default("userscripts", ""),
811         user_stylesheets: user_stylesheets,
812         output_file: opt_match.opt_str("o"),
813         replace_surrogates: debug_options.replace_surrogates,
814         gc_profile: debug_options.gc_profile,
815         load_webfonts_synchronously: debug_options.load_webfonts_synchronously,
816         headless: opt_match.opt_present("z"),
817         hard_fail: opt_match.opt_present("f") && !opt_match.opt_present("F"),
818         bubble_inline_sizes_separately: bubble_inline_sizes_separately,
819         profile_script_events: debug_options.profile_script_events,
820         profile_heartbeats: debug_options.profile_heartbeats,
821         trace_layout: debug_options.trace_layout,
822         debugger_port: debugger_port,
823         devtools_port: devtools_port,
824         webdriver_port: webdriver_port,
825         initial_window_size: initial_window_size,
826         user_agent: user_agent,
827         multiprocess: opt_match.opt_present("M"),
828         sandbox: opt_match.opt_present("S"),
829         random_pipeline_closure_probability: random_pipeline_closure_probability,
830         random_pipeline_closure_seed: random_pipeline_closure_seed,
831         show_debug_fragment_borders: debug_options.show_fragment_borders,
832         show_debug_parallel_layout: debug_options.show_parallel_layout,
833         enable_text_antialiasing: !debug_options.disable_text_aa,
834         enable_subpixel_text_antialiasing: !debug_options.disable_subpixel_aa,
835         enable_canvas_antialiasing: !debug_options.disable_canvas_aa,
836         dump_style_tree: debug_options.dump_style_tree,
837         dump_rule_tree: debug_options.dump_rule_tree,
838         dump_flow_tree: debug_options.dump_flow_tree,
839         dump_display_list: debug_options.dump_display_list,
840         dump_display_list_json: debug_options.dump_display_list_json,
841         relayout_event: debug_options.relayout_event,
842         disable_share_style_cache: debug_options.disable_share_style_cache,
843         style_sharing_stats: debug_options.style_sharing_stats,
844         convert_mouse_to_touch: debug_options.convert_mouse_to_touch,
845         exit_after_load: opt_match.opt_present("x"),
846         no_native_titlebar: do_not_use_native_titlebar,
847         enable_vsync: !debug_options.disable_vsync,
848         webrender_stats: debug_options.webrender_stats,
849         use_msaa: debug_options.use_msaa,
850         config_dir: opt_match.opt_str("config-dir").map(Into::into),
851         full_backtraces: debug_options.full_backtraces,
852         is_printing_version: is_printing_version,
853         webrender_debug: debug_options.webrender_debug,
854         webrender_record: debug_options.webrender_record,
855         webrender_batch: !debug_options.webrender_disable_batch,
856         precache_shaders: debug_options.precache_shaders,
857         signpost: debug_options.signpost,
858         certificate_path: opt_match.opt_str("certificate-path"),
859         unminify_js: opt_match.opt_present("unminify-js"),
860         print_pwm: opt_match.opt_present("print-pwm"),
861     };
862 
863     set_defaults(opts);
864 
865     // These must happen after setting the default options, since the prefs rely on
866     // on the resource path.
867     // Note that command line preferences have the highest precedence
868 
869     prefs::add_user_prefs();
870 
871     for pref in opt_match.opt_strs("pref").iter() {
872         parse_pref_from_command_line(pref);
873     }
874 
875     if let Some(layout_threads) = layout_threads {
876         PREFS.set("layout.threads", PrefValue::Number(layout_threads as f64));
877     } else if let Some(layout_threads) = PREFS.get("layout.threads").as_string() {
878         PREFS.set("layout.threads", PrefValue::Number(layout_threads.parse::<f64>().unwrap()));
879     } else if *PREFS.get("layout.threads") == PrefValue::Missing {
880         let layout_threads = cmp::max(num_cpus::get() * 3 / 4, 1);
881         PREFS.set("layout.threads", PrefValue::Number(layout_threads as f64));
882     }
883 
884     ArgumentParsingResult::ChromeProcess
885 }
886 
887 pub enum ArgumentParsingResult {
888     ChromeProcess,
889     ContentProcess(String),
890 }
891 
892 // Make Opts available globally. This saves having to clone and pass
893 // opts everywhere it is used, which gets particularly cumbersome
894 // when passing through the DOM structures.
895 static mut DEFAULT_OPTIONS: *mut Opts = 0 as *mut Opts;
896 const INVALID_OPTIONS: *mut Opts = 0x01 as *mut Opts;
897 
898 lazy_static! {
899     static ref OPTIONS: Opts = {
900         unsafe {
901             let initial = if !DEFAULT_OPTIONS.is_null() {
902                 let opts = Box::from_raw(DEFAULT_OPTIONS);
903                 *opts
904             } else {
905                 default_opts()
906             };
907             DEFAULT_OPTIONS = INVALID_OPTIONS;
908             initial
909         }
910     };
911 }
912 
set_defaults(opts: Opts)913 pub fn set_defaults(opts: Opts) {
914     unsafe {
915         assert!(DEFAULT_OPTIONS.is_null());
916         assert_ne!(DEFAULT_OPTIONS, INVALID_OPTIONS);
917         let box_opts = Box::new(opts);
918         DEFAULT_OPTIONS = Box::into_raw(box_opts);
919     }
920 }
921 
parse_pref_from_command_line(pref: &str)922 pub fn parse_pref_from_command_line(pref: &str) {
923     let split: Vec<&str> = pref.splitn(2, '=').collect();
924     let pref_name = split[0];
925     let value = split.get(1);
926     match value {
927         Some(&"false") => PREFS.set(pref_name, PrefValue::Boolean(false)),
928         Some(&"true") | None => PREFS.set(pref_name, PrefValue::Boolean(true)),
929         Some(value) => match value.parse::<f64>() {
930             Ok(v) => PREFS.set(pref_name, PrefValue::Number(v)),
931             Err(_) => PREFS.set(pref_name, PrefValue::String(value.to_string()))
932         }
933     };
934 }
935 
936 #[inline]
get() -> &'static Opts937 pub fn get() -> &'static Opts {
938     &OPTIONS
939 }
940 
parse_url_or_filename(cwd: &Path, input: &str) -> Result<ServoUrl, ()>941 pub fn parse_url_or_filename(cwd: &Path, input: &str) -> Result<ServoUrl, ()> {
942     match ServoUrl::parse(input) {
943         Ok(url) => Ok(url),
944         Err(url::ParseError::RelativeUrlWithoutBase) => {
945             Url::from_file_path(&*cwd.join(input)).map(ServoUrl::from_url)
946         }
947         Err(_) => Err(()),
948     }
949 }
950 
951 impl Opts {
should_use_osmesa(&self) -> bool952     pub fn should_use_osmesa(&self) -> bool {
953         self.headless
954     }
955 }
956