Files
Jonathan Schwender 0ea42bc774 profile: Add instrumentation to startup related functions (#44456)
Follow-up to #44443.
This helps investigating the cold-start timeline, and could be used
by tooling to A/B compare branches affecting the cold-start time.

Additionally also change the `handle_request::select` span, so that we
can see the blocked time (which was probably what was intended), since
the actual time spent on recv after select is insignificant.

Testing: Tracing output is not covered by automatic tests.

---------

Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com>
2026-04-23 11:47:27 +00:00

230 lines
7.4 KiB
Rust

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Application entry point, runs the event loop.
use std::rc::Rc;
use std::time::Instant;
use std::{env, fs};
use servo::protocol_handler::ProtocolRegistry;
use servo::{EventLoopWaker, Opts, Preferences, ServoBuilder, ServoUrl, UserContentManager};
use url::Url;
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoopProxy};
use winit::window::WindowId;
use super::event_loop::AppEvent;
use crate::desktop::event_loop::ServoShellEventLoop;
use crate::desktop::headed_window::HeadedWindow;
use crate::desktop::headless_window::HeadlessWindow;
use crate::desktop::protocols;
use crate::desktop::tracing::trace_winit_event;
use crate::parser::get_default_url;
use crate::prefs::ServoShellPreferences;
use crate::running_app_state::RunningAppState;
#[cfg(feature = "gamepad")]
use crate::running_app_state::ServoshellGamepadDelegate;
use crate::window::{PlatformWindow, ServoShellWindowId};
pub(crate) enum AppState {
Initializing,
Running(Rc<RunningAppState>),
ShuttingDown,
}
pub struct App {
opts: Opts,
preferences: Preferences,
servoshell_preferences: ServoShellPreferences,
waker: Box<dyn EventLoopWaker>,
event_loop_proxy: Option<EventLoopProxy<AppEvent>>,
initial_url: ServoUrl,
t_start: Instant,
t: Instant,
state: AppState,
}
impl App {
pub fn new(
opts: Opts,
preferences: Preferences,
servo_shell_preferences: ServoShellPreferences,
event_loop: &ServoShellEventLoop,
) -> Self {
let initial_url = get_default_url(
servo_shell_preferences.url.as_deref(),
env::current_dir().unwrap(),
|path| fs::metadata(path).is_ok(),
&servo_shell_preferences,
);
let t = Instant::now();
App {
opts,
preferences,
servoshell_preferences: servo_shell_preferences,
waker: event_loop.create_event_loop_waker(),
event_loop_proxy: event_loop.event_loop_proxy(),
initial_url,
t_start: t,
t,
state: AppState::Initializing,
}
}
/// Initialize Application once event loop start running.
pub fn init(&mut self, active_event_loop: Option<&ActiveEventLoop>) {
let mut protocol_registry = ProtocolRegistry::default();
let _ = protocol_registry.register(
"urlinfo",
protocols::urlinfo::UrlInfoProtocolHander::default(),
);
let _ =
protocol_registry.register("servo", protocols::servo::ServoProtocolHandler::default());
let _ = protocol_registry.register(
"resource",
protocols::resource::ResourceProtocolHandler::default(),
);
let servo_builder = ServoBuilder::default()
.opts(self.opts.clone())
.preferences(self.preferences.clone())
.protocol_registry(protocol_registry)
.event_loop_waker(self.waker.clone());
let url = self.initial_url.as_url().clone();
let platform_window = self.create_platform_window(url, active_event_loop);
#[cfg(feature = "webxr")]
let servo_builder =
servo_builder.webxr_registry(super::webxr::XrDiscoveryWebXrRegistry::new_boxed(
platform_window.clone(),
active_event_loop,
&self.preferences,
));
let servo = servo_builder.build();
servo.setup_logging();
let user_content_manager = Rc::new(UserContentManager::new(&servo));
for user_stylesheet in &self.servoshell_preferences.user_stylesheets {
user_content_manager.add_stylesheet(user_stylesheet.clone());
}
let running_state = Rc::new(RunningAppState::new(
servo,
self.servoshell_preferences.clone(),
self.waker.clone(),
user_content_manager,
self.preferences.clone(),
#[cfg(feature = "gamepad")]
ServoshellGamepadDelegate::maybe_new().map(Rc::new),
));
running_state.open_window(platform_window, self.initial_url.as_url().clone());
self.state = AppState::Running(running_state);
}
#[servo::servo_tracing::instrument(level = "debug", skip_all)]
fn create_platform_window(
&self,
url: Url,
active_event_loop: Option<&ActiveEventLoop>,
) -> Rc<dyn PlatformWindow> {
assert_eq!(
self.servoshell_preferences.headless,
active_event_loop.is_none()
);
let Some(active_event_loop) = active_event_loop else {
return HeadlessWindow::new(&self.servoshell_preferences);
};
HeadedWindow::new(
&self.servoshell_preferences,
active_event_loop,
self.event_loop_proxy
.clone()
.expect("Should always have event loop proxy in headed mode."),
url,
)
}
pub fn pump_servo_event_loop(&mut self, active_event_loop: Option<&ActiveEventLoop>) -> bool {
let AppState::Running(state) = &self.state else {
return false;
};
let create_platform_window = |url: Url| self.create_platform_window(url, active_event_loop);
if !state.spin_event_loop(Some(&create_platform_window)) {
self.state = AppState::ShuttingDown;
return false;
}
true
}
}
impl ApplicationHandler<AppEvent> for App {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
self.init(Some(event_loop));
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
window_event: WindowEvent,
) {
let now = Instant::now();
trace_winit_event!(
window_event,
"@{:?} (+{:?}) {window_event:?}",
now - self.t_start,
now - self.t
);
self.t = now;
let AppState::Running(state) = &self.state else {
return;
};
if let Some(window) = state.window(ServoShellWindowId::from(u64::from(window_id))) {
if let Some(headed_window) = window.platform_window().as_headed_window() {
headed_window.handle_winit_window_event(state.clone(), window, window_event);
}
}
if !self.pump_servo_event_loop(event_loop.into()) {
event_loop.exit();
}
// Block until the window gets an event
event_loop.set_control_flow(ControlFlow::Wait);
}
fn user_event(&mut self, event_loop: &ActiveEventLoop, app_event: AppEvent) {
let AppState::Running(state) = &self.state else {
return;
};
if let Some(window) = app_event
.window_id()
.and_then(|window_id| state.window(ServoShellWindowId::from(u64::from(window_id))))
{
if let Some(headed_window) = window.platform_window().as_headed_window() {
headed_window.handle_winit_app_event(state.clone(), app_event);
}
}
if !self.pump_servo_event_loop(event_loop.into()) {
event_loop.exit();
}
// Block until the window gets an event
event_loop.set_control_flow(ControlFlow::Wait);
}
}