Files
servo/ports/servoshell/desktop/app.rs
Luke Warlow 64041ee394 Print action requests from our subtree
Co-authored-by: Delan Azabani <dazabani@igalia.com>
Signed-off-by: Luke Warlow <lwarlow@igalia.com>
2026-01-13 17:04:45 +08:00

245 lines
7.8 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::path::Path;
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, UserScript,
};
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;
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: initial_url.clone(),
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 script in load_userscripts(self.servoshell_preferences.userscripts_directory.as_deref())
.expect("Loading userscripts failed")
{
user_content_manager.add_script(script);
}
let running_state = Rc::new(RunningAppState::new(
servo,
self.servoshell_preferences.clone(),
self.waker.clone(),
user_content_manager,
));
running_state.open_window(platform_window, self.initial_url.as_url().clone());
self.state = AppState::Running(running_state);
}
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(&window, 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);
}
}
fn load_userscripts(userscripts_directory: Option<&Path>) -> std::io::Result<Vec<UserScript>> {
let mut userscripts = Vec::new();
if let Some(userscripts_directory) = &userscripts_directory {
let mut files = std::fs::read_dir(userscripts_directory)?
.map(|e| e.map(|entry| entry.path()))
.collect::<Result<Vec<_>, _>>()?;
files.sort_unstable();
for file in files {
userscripts.push(UserScript {
script: std::fs::read_to_string(&file)?,
source_file: Some(file),
});
}
}
Ok(userscripts)
}