servo_config/opts.rs
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 https://mozilla.org/MPL/2.0/. */
4
5//! Options are global configuration options that are initialized once and cannot be changed at
6//! runtime.
7
8use core::str::FromStr;
9use std::default::Default;
10use std::path::PathBuf;
11use std::sync::OnceLock;
12
13use serde::{Deserialize, Serialize};
14use strum::{
15 AsRefStr, Display as EnumDisplay, EnumCount, EnumIter, EnumMessage, EnumString,
16 IntoEnumIterator,
17};
18
19/// The set of global options supported by Servo. The values for these can be configured during
20/// initialization of Servo and cannot be changed later at runtime.
21#[derive(Clone, Debug, Deserialize, Serialize)]
22pub struct Opts {
23 /// `None` to disable the time profiler or `Some` to enable it with either:
24 ///
25 /// - an interval in seconds to cause it to produce output on that interval.
26 /// - a file path to write profiling info to a TSV file upon Servo's termination.
27 pub time_profiling: Option<OutputOptions>,
28
29 /// When the profiler is enabled, this is an optional path to dump a self-contained HTML file
30 /// visualizing the traces as a timeline.
31 pub time_profiler_trace_path: Option<String>,
32
33 /// True to exit on thread failure instead of displaying about:failure.
34 pub hard_fail: bool,
35
36 /// Debug options that are used by developers to control Servo
37 /// behavior for debugging purposes.
38 pub debug: DiagnosticsLogging,
39
40 /// Whether we're running in multiprocess mode.
41 pub multiprocess: bool,
42
43 /// Whether to force using ipc_channel instead of crossbeam_channel in singleprocess mode. Does
44 /// nothing in multiprocess mode.
45 pub force_ipc: bool,
46
47 /// Whether we want background hang monitor enabled or not
48 pub background_hang_monitor: bool,
49
50 /// Whether we're running inside the sandbox.
51 pub sandbox: bool,
52
53 /// Probability of randomly closing a pipeline,
54 /// used for testing the hardening of the constellation.
55 pub random_pipeline_closure_probability: Option<f32>,
56
57 /// The seed for the RNG used to randomly close pipelines,
58 /// used for testing the hardening of the constellation.
59 pub random_pipeline_closure_seed: Option<usize>,
60
61 /// Load shaders from disk.
62 pub shaders_path: Option<PathBuf>,
63
64 /// Directory for a default config directory
65 pub config_dir: Option<PathBuf>,
66
67 /// Use temporary storage (data on disk will not persist across restarts).
68 pub temporary_storage: bool,
69
70 /// Path to PEM encoded SSL CA certificate store.
71 pub certificate_path: Option<String>,
72
73 /// Path to a hosts file (like `/etc/hosts`).
74 /// Ignored if the `HOST_FILE` environment variable is set.
75 pub host_file: Option<PathBuf>,
76
77 /// Whether or not to completely ignore SSL certificate validation errors.
78 /// TODO: We should see if we can eliminate the need for this by fixing
79 /// <https://github.com/servo/servo/issues/30080>.
80 pub ignore_certificate_errors: bool,
81
82 /// Unminify Javascript.
83 pub unminify_js: bool,
84
85 /// Directory path that was created with "unminify-js"
86 pub local_script_source: Option<String>,
87
88 /// Unminify Css.
89 pub unminify_css: bool,
90}
91
92/// The set of diagnostic options that can be enabled in Servo.
93#[derive(
94 AsRefStr,
95 Copy,
96 Clone,
97 Debug,
98 EnumCount,
99 EnumDisplay,
100 EnumIter,
101 EnumMessage,
102 EnumString,
103 PartialEq,
104)]
105pub enum DiagnosticsLoggingOption {
106 /// Log the DOM after each restyle
107 #[strum(to_string = "style-tree")]
108 StyleTree,
109
110 /// Log the rule tree
111 #[strum(to_string = "rule-tree")]
112 RuleTree,
113
114 /// Log the fragment tree after each layout
115 #[strum(to_string = "flow-tree")]
116 FlowTree,
117
118 /// Log the stacking context tree after each layout
119 #[strum(to_string = "stacking-context-tree")]
120 StackingContextTree,
121
122 /// Log the scroll tree (the hierarchy of scrollable areas) after each layout
123 #[strum(to_string = "scroll-tree")]
124 ScrollTree,
125
126 /// Log the display list after each layout
127 #[strum(to_string = "display-list")]
128 DisplayList,
129
130 /// Log notifications when a relayout occurs
131 #[strum(to_string = "relayout-event")]
132 RelayoutEvent,
133
134 /// Periodically log on which events script threads spend their processing time
135 #[strum(to_string = "profile-script-events")]
136 ProfileScriptEvents,
137
138 /// Log the the hit/miss statistics for the style sharing cache after each restyle
139 #[strum(to_string = "style-stats")]
140 StyleStatistics,
141
142 /// Log garbage collection passes and their durations
143 #[strum(to_string = "gc-profile")]
144 GcProfile,
145
146 /// Log Progressive Web Metrics
147 #[strum(to_string = "progressive-web-metrics")]
148 ProgressiveWebMetrics,
149}
150
151impl DiagnosticsLoggingOption {
152 /// Returns a string representation of this variant that is compatible with
153 /// [`FromStr::from_str`] and [`DiagnosticsLogging::extend_from_string`].
154 /// This value can be used as a command-line argument for an application.
155 pub fn help_option(&self) -> &str {
156 self.as_ref()
157 }
158
159 /// Returns a string with a short description of the diagnostic option.
160 /// This value can be used as a command-line argument description for an application.
161 pub fn help_message(&self) -> &str {
162 self.get_documentation()
163 .expect("all variants of `DiagnosticsLoggingOption` should have a help message")
164 }
165
166 /// Returns an `Iterator` that can be used to iterate over all the diagnostic options
167 /// supported by Servo. This is useful when constructing a help message that enumerates
168 /// all possible diagnostic flags and their respective help messages.
169 pub fn iter() -> impl Iterator<Item = Self> {
170 <Self as IntoEnumIterator>::iter()
171 }
172}
173
174/// The current configuration of the diagnostic options for Servo.
175#[derive(Clone, Debug, Default, Deserialize, Serialize)]
176pub struct DiagnosticsLogging {
177 options: [bool; DiagnosticsLoggingOption::COUNT],
178}
179
180impl DiagnosticsLogging {
181 /// Create a new DiagnosticsLogging configuration.
182 ///
183 /// In builds with debug assertions enabled, this will automatically read and parse the
184 /// SERVO_DIAGNOSTICS environment variable if it is set.
185 pub fn new() -> Self {
186 #[cfg(not(debug_assertions))]
187 return DiagnosticsLogging::default();
188
189 // Disabled for production and release builds
190 #[cfg(debug_assertions)]
191 {
192 let mut config: DiagnosticsLogging = Default::default();
193 if let Ok(diagnostics_var) = std::env::var("SERVO_DIAGNOSTICS") &&
194 let Err(error) = config.extend_from_string(&diagnostics_var)
195 {
196 eprintln!("Could not parse debug logging option: {error}");
197 };
198 config
199 }
200 }
201
202 /// Enables or disables the diagnostics represented by the given [`DiagnosticsLoggingOption`]
203 /// variant.
204 pub fn toggle_option(&mut self, option: DiagnosticsLoggingOption, enabled: bool) {
205 self.options[option as usize] = enabled;
206 }
207
208 /// Returns true if the given diagnostic option is enabled.
209 pub fn is_enabled(&self, option: DiagnosticsLoggingOption) -> bool {
210 self.options[option as usize]
211 }
212
213 /// Extend the current configuration with additional options.
214 ///
215 /// Parses the string and merges any enabled options into the current configuration.
216 pub fn extend_from_string(&mut self, option_string: &str) -> Result<(), String> {
217 for option in option_string.split(',') {
218 let option = option.trim();
219 match DiagnosticsLoggingOption::from_str(option) {
220 Ok(flag) => self.toggle_option(flag, true),
221 Err(_) => return Err(format!("Unknown diagnostic option: {option}")),
222 };
223 }
224
225 Ok(())
226 }
227}
228
229/// The destination for the time profiler reports.
230#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
231pub enum OutputOptions {
232 FileName(String),
233 Stdout(f64),
234}
235
236impl Default for Opts {
237 fn default() -> Self {
238 Self {
239 time_profiling: None,
240 time_profiler_trace_path: None,
241 hard_fail: true,
242 multiprocess: false,
243 force_ipc: false,
244 background_hang_monitor: false,
245 random_pipeline_closure_probability: None,
246 random_pipeline_closure_seed: None,
247 sandbox: false,
248 debug: Default::default(),
249 config_dir: None,
250 temporary_storage: false,
251 shaders_path: None,
252 certificate_path: None,
253 host_file: None,
254 ignore_certificate_errors: false,
255 unminify_js: false,
256 local_script_source: None,
257 unminify_css: false,
258 }
259 }
260}
261
262// Make Opts available globally. This saves having to clone and pass
263// opts everywhere it is used, which gets particularly cumbersome
264// when passing through the DOM structures.
265static OPTIONS: OnceLock<Opts> = OnceLock::new();
266
267/// Initialize options.
268///
269/// Should only be called once at process startup.
270/// Must be called before the first call to [`get`].
271pub fn initialize_options(opts: Opts) {
272 OPTIONS.set(opts).expect("Already initialized");
273}
274
275/// Get the servo options
276///
277/// If the servo options have not been initialized by calling [`initialize_options`], then the
278/// options will be initialized to default values. Outside of tests the options should be
279/// explicitly initialized.
280#[inline]
281pub fn get() -> &'static Opts {
282 // In unit-tests using default options reduces boilerplate.
283 // We can't use `cfg(test)` since that only is enabled when this crate
284 // is compiled in test mode.
285 // We rely on the `expect` in `initialize_options` to inform us if refactoring
286 // causes a `get` call to move before `initialize_options`.
287 OPTIONS.get_or_init(Default::default)
288}
289
290#[test]
291fn test_parsing_of_diagnostics_logging_options() {
292 assert!(DiagnosticsLoggingOption::iter().collect::<Vec<_>>().len() > 0);
293
294 let mut diagnostics = DiagnosticsLogging::new();
295 for option in DiagnosticsLoggingOption::iter() {
296 assert_eq!(diagnostics.is_enabled(option), false);
297 }
298
299 assert!(
300 diagnostics
301 .extend_from_string("profile-script-events,style-stats")
302 .is_ok()
303 );
304 assert!(diagnostics.is_enabled(DiagnosticsLoggingOption::ProfileScriptEvents));
305 assert!(diagnostics.is_enabled(DiagnosticsLoggingOption::StyleStatistics));
306 assert!(!diagnostics.is_enabled(DiagnosticsLoggingOption::ProgressiveWebMetrics));
307
308 assert!(
309 diagnostics
310 .extend_from_string("profile-script-events,syle-stats")
311 .is_err()
312 );
313
314 let mut diagnostics = DiagnosticsLogging::new();
315 for option in DiagnosticsLoggingOption::iter() {
316 assert_eq!(
317 DiagnosticsLoggingOption::from_str(option.help_option()),
318 Ok(option)
319 );
320
321 assert!(!diagnostics.is_enabled(option));
322 assert!(diagnostics.extend_from_string(option.help_option()).is_ok());
323 assert!(diagnostics.is_enabled(option),);
324 }
325}