Skip to main content

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    /// Log the accessibility tree
151    #[strum(to_string = "accessibility-tree")]
152    AccessibilityTree,
153}
154
155impl DiagnosticsLoggingOption {
156    /// Returns a string representation of this variant that is compatible with
157    /// [`FromStr::from_str`] and [`DiagnosticsLogging::extend_from_string`].
158    /// This value can be used as a command-line argument for an application.
159    pub fn help_option(&self) -> &str {
160        self.as_ref()
161    }
162
163    /// Returns a string with a short description of the diagnostic option.
164    /// This value can be used as a command-line argument description for an application.
165    pub fn help_message(&self) -> &str {
166        self.get_documentation()
167            .expect("all variants of `DiagnosticsLoggingOption` should have a help message")
168    }
169
170    /// Returns an `Iterator` that can be used to iterate over all the diagnostic options
171    /// supported by Servo. This is useful when constructing a help message that enumerates
172    /// all possible diagnostic flags and their respective help messages.
173    pub fn iter() -> impl Iterator<Item = Self> {
174        <Self as IntoEnumIterator>::iter()
175    }
176}
177
178/// The current configuration of the diagnostic options for Servo.
179#[derive(Clone, Debug, Default, Deserialize, Serialize)]
180pub struct DiagnosticsLogging {
181    options: [bool; DiagnosticsLoggingOption::COUNT],
182}
183
184impl DiagnosticsLogging {
185    /// Create a new DiagnosticsLogging configuration.
186    ///
187    /// In builds with debug assertions enabled, this will automatically read and parse the
188    /// SERVO_DIAGNOSTICS environment variable if it is set.
189    pub fn new() -> Self {
190        #[cfg(not(debug_assertions))]
191        return DiagnosticsLogging::default();
192
193        // Disabled for production and release builds
194        #[cfg(debug_assertions)]
195        {
196            let mut config: DiagnosticsLogging = Default::default();
197            if let Ok(diagnostics_var) = std::env::var("SERVO_DIAGNOSTICS") &&
198                let Err(error) = config.extend_from_string(&diagnostics_var)
199            {
200                eprintln!("Could not parse debug logging option: {error}");
201            };
202            config
203        }
204    }
205
206    /// Enables or disables the diagnostics represented by the given [`DiagnosticsLoggingOption`]
207    /// variant.
208    pub fn toggle_option(&mut self, option: DiagnosticsLoggingOption, enabled: bool) {
209        self.options[option as usize] = enabled;
210    }
211
212    /// Returns true if the given diagnostic option is enabled.
213    pub fn is_enabled(&self, option: DiagnosticsLoggingOption) -> bool {
214        self.options[option as usize]
215    }
216
217    /// Extend the current configuration with additional options.
218    ///
219    /// Parses the string and merges any enabled options into the current configuration.
220    pub fn extend_from_string(&mut self, option_string: &str) -> Result<(), String> {
221        for option in option_string.split(',') {
222            let option = option.trim();
223            match DiagnosticsLoggingOption::from_str(option) {
224                Ok(flag) => self.toggle_option(flag, true),
225                Err(_) => return Err(format!("Unknown diagnostic option: {option}")),
226            };
227        }
228
229        Ok(())
230    }
231}
232
233/// The destination for the time profiler reports.
234#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
235pub enum OutputOptions {
236    FileName(String),
237    Stdout(f64),
238}
239
240impl Default for Opts {
241    fn default() -> Self {
242        Self {
243            time_profiling: None,
244            time_profiler_trace_path: None,
245            hard_fail: true,
246            multiprocess: false,
247            force_ipc: false,
248            background_hang_monitor: false,
249            random_pipeline_closure_probability: None,
250            random_pipeline_closure_seed: None,
251            sandbox: false,
252            debug: Default::default(),
253            config_dir: None,
254            temporary_storage: false,
255            shaders_path: None,
256            certificate_path: None,
257            host_file: None,
258            ignore_certificate_errors: false,
259            unminify_js: false,
260            local_script_source: None,
261            unminify_css: false,
262        }
263    }
264}
265
266// Make Opts available globally. This saves having to clone and pass
267// opts everywhere it is used, which gets particularly cumbersome
268// when passing through the DOM structures.
269static OPTIONS: OnceLock<Opts> = OnceLock::new();
270
271/// Initialize options.
272///
273/// Should only be called once at process startup.
274/// Must be called before the first call to [`get`].
275pub fn initialize_options(opts: Opts) {
276    OPTIONS.set(opts).expect("Already initialized");
277}
278
279/// Get the servo options
280///
281/// If the servo options have not been initialized by calling [`initialize_options`], then the
282/// options will be initialized to default values. Outside of tests the options should be
283/// explicitly initialized.
284#[inline]
285pub fn get() -> &'static Opts {
286    // In unit-tests using default options reduces boilerplate.
287    // We can't use `cfg(test)` since that only is enabled when this crate
288    // is compiled in test mode.
289    // We rely on the `expect` in `initialize_options` to inform us if refactoring
290    // causes a `get` call to move before `initialize_options`.
291    OPTIONS.get_or_init(Default::default)
292}
293
294#[test]
295fn test_parsing_of_diagnostics_logging_options() {
296    assert!(DiagnosticsLoggingOption::iter().collect::<Vec<_>>().len() > 0);
297
298    let mut diagnostics = DiagnosticsLogging::new();
299    for option in DiagnosticsLoggingOption::iter() {
300        assert_eq!(diagnostics.is_enabled(option), false);
301    }
302
303    assert!(
304        diagnostics
305            .extend_from_string("profile-script-events,style-stats")
306            .is_ok()
307    );
308    assert!(diagnostics.is_enabled(DiagnosticsLoggingOption::ProfileScriptEvents));
309    assert!(diagnostics.is_enabled(DiagnosticsLoggingOption::StyleStatistics));
310    assert!(!diagnostics.is_enabled(DiagnosticsLoggingOption::ProgressiveWebMetrics));
311
312    assert!(
313        diagnostics
314            .extend_from_string("profile-script-events,syle-stats")
315            .is_err()
316    );
317
318    let mut diagnostics = DiagnosticsLogging::new();
319    for option in DiagnosticsLoggingOption::iter() {
320        assert_eq!(
321            DiagnosticsLoggingOption::from_str(option.help_option()),
322            Ok(option)
323        );
324
325        assert!(!diagnostics.is_enabled(option));
326        assert!(diagnostics.extend_from_string(option.help_option()).is_ok());
327        assert!(diagnostics.is_enabled(option),);
328    }
329}