servoshell/
lib.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
5use cfg_if::cfg_if;
6
7#[cfg(test)]
8mod test;
9
10#[cfg(not(target_os = "android"))]
11mod backtrace;
12#[cfg(not(target_env = "ohos"))]
13mod crash_handler;
14#[cfg(not(any(target_os = "android", target_env = "ohos")))]
15pub(crate) mod desktop;
16#[cfg(any(target_os = "android", target_env = "ohos"))]
17mod egl;
18#[cfg(not(any(target_os = "android", target_env = "ohos")))]
19mod panic_hook;
20mod parser;
21mod prefs;
22#[cfg(not(target_os = "android"))]
23mod resources;
24mod running_app_state;
25mod webdriver;
26mod window;
27
28#[cfg(not(any(target_os = "android", target_env = "ohos")))]
29pub(crate) use crate::desktop::gamepad::GamepadSupport;
30#[cfg(any(target_os = "android", target_env = "ohos"))]
31pub(crate) use crate::egl::gamepad::GamepadSupport;
32
33pub mod platform {
34    #[cfg(target_os = "macos")]
35    pub use crate::platform::macos::deinit;
36
37    #[cfg(target_os = "macos")]
38    pub mod macos;
39
40    #[cfg(not(target_os = "macos"))]
41    pub fn deinit(_clean_shutdown: bool) {}
42}
43
44#[cfg(not(any(target_os = "android", target_env = "ohos")))]
45pub fn main() {
46    desktop::cli::main()
47}
48
49pub fn init_crypto() {
50    rustls::crypto::aws_lc_rs::default_provider()
51        .install_default()
52        .expect("Error initializing crypto provider");
53}
54
55pub fn init_tracing(filter_directives: Option<&str>) {
56    #[cfg(not(feature = "tracing"))]
57    {
58        if filter_directives.is_some() {
59            log::debug!("The tracing feature was not selected - ignoring trace filter directives");
60        }
61    }
62    #[cfg(feature = "tracing")]
63    {
64        use tracing_subscriber::layer::SubscriberExt;
65        let subscriber = tracing_subscriber::registry();
66
67        #[cfg(feature = "tracing-perfetto")]
68        let subscriber = {
69            // Set up a PerfettoLayer for performance tracing.
70            // The servo.pftrace file can be uploaded to https://ui.perfetto.dev for analysis.
71            let file = std::fs::File::create("servo.pftrace").unwrap();
72            let perfetto_layer = tracing_perfetto::PerfettoLayer::new(std::sync::Mutex::new(file))
73                .with_filter_by_marker(|field_name| field_name == "servo_profiling")
74                .with_debug_annotations(true);
75            subscriber.with(perfetto_layer)
76        };
77
78        #[cfg(feature = "tracing-hitrace")]
79        let subscriber = {
80            // Set up a HitraceLayer for performance tracing.
81            subscriber.with(HitraceLayer::default())
82        };
83
84        // Filter events and spans by the directives in SERVO_TRACING, using EnvFilter as a global filter.
85        // <https://docs.rs/tracing-subscriber/0.3.18/tracing_subscriber/layer/index.html#global-filtering>
86        let filter_builder = tracing_subscriber::EnvFilter::builder()
87            .with_default_directive(tracing::level_filters::LevelFilter::OFF.into());
88        let filter = if let Some(filters) = &filter_directives {
89            filter_builder.parse_lossy(filters)
90        } else {
91            filter_builder
92                .with_env_var("SERVO_TRACING")
93                .from_env_lossy()
94        };
95
96        let subscriber = subscriber.with(filter);
97
98        // Same as SubscriberInitExt::init, but avoids initialising the tracing-log compat layer,
99        // since it would break Servo’s FromScriptLogger and FromEmbederLogger.
100        // <https://docs.rs/tracing-subscriber/0.3.18/tracing_subscriber/util/trait.SubscriberInitExt.html#method.init>
101        // <https://docs.rs/tracing/0.1.40/tracing/#consuming-log-records>
102        tracing::subscriber::set_global_default(subscriber)
103            .expect("Failed to set tracing subscriber");
104    }
105}
106
107pub const VERSION: &str = concat!("Servo ", env!("CARGO_PKG_VERSION"), "-", env!("GIT_SHA"));
108
109/// Plumbs tracing spans into HiTrace, with the following caveats:
110///
111/// - We ignore spans unless they have a `servo_profiling` field.
112/// - We map span entry ([`Layer::on_enter`]) to `OH_HiTrace_StartTrace(metadata.name())`.
113/// - We map span exit ([`Layer::on_exit`]) to `OH_HiTrace_FinishTrace()`.
114///
115/// As a result, within each thread, spans must exit in reverse order of their entry, otherwise the
116/// resultant profiling data will be incorrect (see the section below). This is not necessarily the
117/// case for tracing spans, since there can be multiple [trace trees], so we check that this
118/// invariant is upheld when debug assertions are enabled, logging errors if it is violated.
119///
120/// [trace trees]: https://docs.rs/tracing/0.1.40/tracing/span/index.html#span-relationships
121///
122/// # Uniquely identifying spans
123///
124/// We need to ensure that the start and end points of one span are not mixed up with other spans.
125/// For now, we use the HiTrace [synchronous API], which restricts how spans must behave.
126///
127/// In the HiTrace [synchronous API], spans must have stack-like behaviour, because spans are keyed
128/// entirely on their *name* string, and OH_HiTrace_FinishTrace always ends the most recent span.
129/// While synchronous API spans are thread-local, callers could still violate this invariant with
130/// reentrant or asynchronous code.
131///
132/// In the [asynchronous API], spans are keyed on a (*name*,*taskId*) pair, where *name* is again
133/// a string, and *taskId* is an arbitrary [`i32`]. This makes *taskId* a good place for a unique
134/// identifier, but asynchronous spans can cross thread boundaries, so the identifier needs to be
135/// temporally unique in the whole process.
136///
137/// Tracing spans have such an identifier ([`Id`]), but they’re [`u64`]-based, and their format
138/// is an internal implementation detail of the [`Subscriber`]. For [`Registry`], those values
139/// [come from] a [packed representation] of a generation number, thread number, page number, and
140/// variable-length index. This makes them hard to compress robustly into an [`i32`].
141///
142/// If we move to the asynchronous API, we will need to generate our own *taskId* values, perhaps
143/// by combining some sort of thread id with a thread-local atomic counter. [`ThreadId`] is opaque
144/// in stable Rust, and converts to a [`u64`] in unstable Rust, so we would also need to make our
145/// own thread ids, perhaps by having a global atomic counter cached in a thread-local.
146///
147/// [synchronous API]: https://docs.rs/hitrace-sys/0.1.2/hitrace_sys/fn.OH_HiTrace_StartTrace.html
148/// [asynchronous API]: https://docs.rs/hitrace-sys/0.1.2/hitrace_sys/fn.OH_HiTrace_StartAsyncTrace.html
149/// [`Registry`]: tracing_subscriber::Registry
150/// [come from]: https://docs.rs/tracing-subscriber/0.3.18/src/tracing_subscriber/registry/sharded.rs.html#237-269
151/// [packed representation]: https://docs.rs/sharded-slab/0.1.7/sharded_slab/trait.Config.html
152/// [`ThreadId`]: std::thread::ThreadId
153#[cfg(feature = "tracing-hitrace")]
154#[derive(Default)]
155struct HitraceLayer {}
156
157cfg_if! {
158    if #[cfg(feature = "tracing-hitrace")] {
159        use std::cell::RefCell;
160
161        use tracing::span::Id;
162        use tracing::Subscriber;
163        use tracing_subscriber::Layer;
164
165        #[cfg(debug_assertions)]
166        thread_local! {
167            /// Stack of span names, to ensure the HiTrace synchronous API is not misused.
168            static HITRACE_NAME_STACK: RefCell<Vec<String>> = RefCell::default();
169        }
170
171        impl<S: Subscriber + for<'lookup> tracing_subscriber::registry::LookupSpan<'lookup>>
172            Layer<S> for HitraceLayer
173        {
174            fn on_enter(&self, id: &Id, ctx: tracing_subscriber::layer::Context<'_, S>) {
175                if let Some(metadata) = ctx.metadata(id) {
176                    // TODO: is this expensive? Would extensions be faster?
177                    // <https://docs.rs/tracing-subscriber/0.3.18/tracing_subscriber/registry/struct.ExtensionsMut.html>
178                    if metadata.fields().field("servo_profiling").is_some() {
179                        #[cfg(debug_assertions)]
180                        HITRACE_NAME_STACK.with_borrow_mut(|stack|
181                            stack.push(metadata.name().to_owned()));
182
183                        hitrace::start_trace(
184                            &std::ffi::CString::new(metadata.name())
185                                .expect("Failed to convert str to CString"),
186                        );
187                    }
188                }
189            }
190
191            fn on_exit(&self, id: &Id, ctx: tracing_subscriber::layer::Context<'_, S>) {
192                if let Some(metadata) = ctx.metadata(id) {
193                    if metadata.fields().field("servo_profiling").is_some() {
194                        hitrace::finish_trace();
195
196                        #[cfg(debug_assertions)]
197                        HITRACE_NAME_STACK.with_borrow_mut(|stack| {
198                            if stack.last().map(|name| &**name) != Some(metadata.name()) {
199                                log::error!(
200                                    "Tracing span out of order: {} (stack: {:?})",
201                                    metadata.name(),
202                                    stack
203                                );
204                            }
205                            if !stack.is_empty() {
206                                stack.pop();
207                            }
208                        });
209                    }
210                }
211            }
212        }
213    }
214}