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