api: Flatten and simplify Servo preferences (#34966)

Flatten and simplify Servo's preferences code. In addition, have both
preferences and options passed in as arguments to `Servo::new()` and
make sure not to use the globally set preferences in `servoshell` (as
much as possible now).

Instead of a complex procedural macro to generate preferences, just
expose a very simple derive macro that adds string based getters and
setters.

- All command-line parsing is moved to servoshell.
- There is no longer the concept of a missing preference.
- Preferences no longer have to be part of the resources bundle because
  they now have reasonable default values.
- servoshell specific preferences are no longer part of the preferences
  exposed by the Servo API.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson 2025-01-14 14:54:06 +01:00 committed by GitHub
parent c4c85affb5
commit 0e616e0c5d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
316 changed files with 2088 additions and 3235 deletions

View file

@ -15,7 +15,8 @@ use raw_window_handle::HasDisplayHandle;
use servo::base::id::WebViewId;
use servo::compositing::windowing::EmbedderEvent;
use servo::compositing::CompositeTarget;
use servo::config::opts;
use servo::config::opts::Opts;
use servo::config::prefs::Preferences;
use servo::embedder_traits::EventLoopWaker;
use servo::servo_config::pref;
use servo::url::ServoUrl;
@ -38,21 +39,22 @@ use crate::desktop::embedder::{EmbedderCallbacks, XrDiscovery};
use crate::desktop::tracing::trace_winit_event;
use crate::desktop::window_trait::WindowPortsMethods;
use crate::parser::get_default_url;
use crate::prefs::ServoShellPreferences;
pub struct App {
opts: Opts,
preferences: Preferences,
servo_shell_preferences: ServoShellPreferences,
servo: Option<Servo<dyn WindowPortsMethods>>,
webviews: Option<WebViewManager<dyn WindowPortsMethods>>,
event_queue: Vec<EmbedderEvent>,
suspended: Cell<bool>,
windows: HashMap<WindowId, Rc<dyn WindowPortsMethods>>,
minibrowser: Option<Minibrowser>,
user_agent: Option<String>,
waker: Box<dyn EventLoopWaker>,
initial_url: ServoUrl,
t_start: Instant,
t: Instant,
do_not_use_native_titlebar: bool,
device_pixel_ratio_override: Option<f32>,
}
enum Present {
@ -73,38 +75,40 @@ enum PumpResult {
impl App {
pub fn new(
opts: Opts,
preferences: Preferences,
servo_shell_preferences: ServoShellPreferences,
events_loop: &EventsLoop,
user_agent: Option<String>,
url: Option<String>,
do_not_use_native_titlebar: bool,
device_pixel_ratio_override: Option<f32>,
) -> Self {
// Handle browser state.
let initial_url = get_default_url(url.as_deref(), env::current_dir().unwrap(), |path| {
fs::metadata(path).is_ok()
});
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,
servo_shell_preferences,
event_queue: vec![],
webviews: None,
servo: None,
suspended: Cell::new(false),
windows: HashMap::new(),
minibrowser: None,
user_agent,
waker: events_loop.create_event_loop_waker(),
initial_url: initial_url.clone(),
t_start: t,
t,
do_not_use_native_titlebar,
device_pixel_ratio_override,
}
}
/// Initialize Application once event loop start running.
pub fn init(&mut self, event_loop: Option<&ActiveEventLoop>) {
// Create rendering context
let rendering_context = if opts::get().headless {
let rendering_context = if self.opts.headless {
let connection = Connection::new().expect("Failed to create connection");
let adapter = connection
.create_software_adapter()
@ -112,7 +116,7 @@ impl App {
RenderingContext::create(
&connection,
&adapter,
Some(opts::get().initial_window_size.to_untyped().to_i32()),
Some(self.opts.initial_window_size.to_untyped().to_i32()),
)
.expect("Failed to create WR surfman")
} else {
@ -129,18 +133,20 @@ impl App {
.expect("Failed to create WR surfman")
};
let window = if opts::get().headless {
let window = if self.opts.headless {
headless_window::Window::new(
opts::get().initial_window_size,
self.device_pixel_ratio_override,
self.opts.initial_window_size,
self.servo_shell_preferences.device_pixel_ratio_override,
self.opts.screen_size_override,
)
} else {
Rc::new(headed_window::Window::new(
&self.opts,
&rendering_context,
opts::get().initial_window_size,
self.opts.initial_window_size,
event_loop.unwrap(),
self.do_not_use_native_titlebar,
self.device_pixel_ratio_override,
self.servo_shell_preferences.no_native_titlebar,
self.servo_shell_preferences.device_pixel_ratio_override,
))
};
@ -171,7 +177,7 @@ impl App {
self.event_queue.push(EmbedderEvent::Idle);
let (_, window) = self.windows.iter().next().unwrap();
let xr_discovery = if pref!(dom.webxr.openxr.enabled) && !opts::get().headless {
let xr_discovery = if pref!(dom_webxr_openxr_enabled) && !self.opts.headless {
#[cfg(target_os = "windows")]
let openxr = {
let app_info = AppInfo::new("Servoshell", 0, "Servo", 0);
@ -181,7 +187,7 @@ impl App {
let openxr = None;
openxr
} else if pref!(dom.webxr.glwindow.enabled) && !opts::get().headless {
} else if pref!(dom_webxr_glwindow_enabled) && !self.opts.headless {
let window = window.new_glwindow(event_loop.unwrap());
Some(XrDiscovery::GlWindow(GlWindowDiscovery::new(window)))
} else {
@ -198,10 +204,12 @@ impl App {
CompositeTarget::Window
};
let mut servo = Servo::new(
self.opts.clone(),
self.preferences.clone(),
rendering_context,
embedder,
window.clone(),
self.user_agent.clone(),
self.servo_shell_preferences.user_agent.clone(),
composite_target,
);
@ -243,7 +251,7 @@ impl App {
// If the Gamepad API is enabled, handle gamepad events from GilRs.
// Checking for focused_webview_id should ensure we'll have a valid browsing context.
if pref!(dom.gamepad.enabled) && webviews.focused_webview_id().is_some() {
if pref!(dom_gamepad_enabled) && webviews.focused_webview_id().is_some() {
webviews.handle_gamepad_events();
}
@ -254,7 +262,7 @@ impl App {
let mut need_update = false;
loop {
// Consume and handle those embedder messages.
let servo_event_response = webviews.handle_servo_events(embedder_messages);
let servo_event_response = webviews.handle_servo_events(&self.opts, embedder_messages);
need_present |= servo_event_response.need_present;
need_update |= servo_event_response.need_update;
@ -524,7 +532,11 @@ impl ApplicationHandler<WakerEvent> for App {
if let Some(ref minibrowser) = self.minibrowser {
let webviews = &mut self.webviews.as_mut().unwrap();
let app_event_queue = &mut self.event_queue;
minibrowser.queue_embedder_events_for_minibrowser_events(webviews, app_event_queue);
minibrowser.queue_embedder_events_for_minibrowser_events(
webviews,
app_event_queue,
&self.servo_shell_preferences,
);
}
self.handle_events_with_winit(event_loop, window);
@ -565,7 +577,11 @@ impl ApplicationHandler<WakerEvent> for App {
if let Some(ref minibrowser) = self.minibrowser {
let webviews = &mut self.webviews.as_mut().unwrap();
let app_event_queue = &mut self.event_queue;
minibrowser.queue_embedder_events_for_minibrowser_events(webviews, app_event_queue);
minibrowser.queue_embedder_events_for_minibrowser_events(
webviews,
app_event_queue,
&self.servo_shell_preferences,
);
}
self.handle_events_with_winit(event_loop, window);

View file

@ -2,16 +2,12 @@
* 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/. */
use std::{env, panic, process};
use getopts::Options;
use log::error;
use servo::config::opts::{self, ArgumentParsingResult};
use servo::servo_config::pref;
use std::{env, panic};
use crate::desktop::app::App;
use crate::desktop::events_loop::EventsLoop;
use crate::panic_hook;
use crate::prefs::{parse_command_line_arguments, ArgumentParsingResult};
pub fn main() {
crate::crash_handler::install();
@ -19,98 +15,23 @@ pub fn main() {
crate::init_crypto();
crate::resources::init();
// Parse the command line options and store them globally
let args: Vec<String> = env::args().collect();
let mut opts = Options::new();
opts.optflag(
"",
"clean-shutdown",
"Do not shutdown until all threads have finished (macos only)",
);
opts.optflag("b", "no-native-titlebar", "Do not use native titlebar");
opts.optopt("", "device-pixel-ratio", "Device pixels per px", "");
opts.optopt(
"u",
"user-agent",
"Set custom user agent string (or ios / android / desktop for platform default)",
"NCSA Mosaic/1.0 (X11;SunOS 4.1.4 sun4m)",
);
opts.optmulti(
"",
"pref",
"A preference to set to enable",
"dom.bluetooth.enabled",
);
opts.optmulti(
"",
"pref",
"A preference to set to disable",
"dom.webgpu.enabled=false",
);
opts.optmulti(
"",
"prefs-file",
"Load in additional prefs from a file.",
"--prefs-file /path/to/prefs.json",
);
let opts_matches;
let content_process_token;
match opts::from_cmdline_args(opts, &args) {
ArgumentParsingResult::ContentProcess(matches, token) => {
opts_matches = matches;
content_process_token = Some(token);
},
ArgumentParsingResult::ChromeProcess(matches) => {
opts_matches = matches;
content_process_token = None;
},
};
crate::prefs::register_user_prefs(&opts_matches);
// TODO: once log-panics is released, can this be replaced by
// log_panics::init()?
panic::set_hook(Box::new(panic_hook::panic_hook));
if let Some(token) = content_process_token {
return servo::run_content_process(token);
}
if opts::get().is_printing_version {
println!("{}", crate::servo_version());
process::exit(0);
}
let clean_shutdown = opts_matches.opt_present("clean-shutdown");
let do_not_use_native_titlebar =
opts_matches.opt_present("no-native-titlebar") || !(pref!(shell.native_titlebar.enabled));
let device_pixel_ratio_override = opts_matches.opt_str("device-pixel-ratio").map(|dppx_str| {
dppx_str.parse().unwrap_or_else(|err| {
error!("Error parsing option: --device-pixel-ratio ({})", err);
process::exit(1);
})
});
let user_agent = opts_matches.opt_str("u");
let url_opt = if !opts_matches.free.is_empty() {
Some(&opts_matches.free[0][..])
} else {
None
let args = env::args().collect();
let (opts, preferences, servoshell_preferences) = match parse_command_line_arguments(args) {
ArgumentParsingResult::ContentProcess(token) => return servo::run_content_process(token),
ArgumentParsingResult::ChromeProcess(opts, preferences, servoshell_preferences) => {
(opts, preferences, servoshell_preferences)
},
};
let event_loop = EventsLoop::new(opts::get().headless, opts::get().output_file.is_some())
let clean_shutdown = servoshell_preferences.clean_shutdown;
let event_loop = EventsLoop::new(opts.headless, opts.output_file.is_some())
.expect("Failed to create events loop");
let mut app = App::new(
&event_loop,
user_agent,
url_opt.map(|s| s.to_string()),
do_not_use_native_titlebar,
device_pixel_ratio_override,
);
let mut app = App::new(opts, preferences, servoshell_preferences, &event_loop);
event_loop.run_app(&mut app);

View file

@ -48,7 +48,7 @@ impl EmbedderMethods for EmbedderCallbacks {
xr: &mut webxr::MainThreadRegistry,
_embedder_proxy: EmbedderProxy,
) {
if pref!(dom.webxr.test) {
if pref!(dom_webxr_test) {
xr.register_mock(webxr::headless::HeadlessMockDiscovery::new());
} else if let Some(xr_discovery) = self.xr_discovery.take() {
match xr_discovery {

View file

@ -8,7 +8,6 @@ use std::sync::{Arc, Condvar, Mutex};
use std::time;
use log::warn;
use servo::config::{pref, set_pref};
use servo::embedder_traits::EventLoopWaker;
use winit::error::EventLoopError;
use winit::event_loop::EventLoop as WinitEventLoop;
@ -90,10 +89,6 @@ impl EventsLoop {
},
EventLoop::Headless(ref data) => {
let (flag, condvar) = &**data;
if pref!(media.glvideo.enabled) {
warn!("GL video rendering is not supported on headless windows.");
set_pref!(media.glvideo.enabled, false);
}
app.init(None);
loop {

View file

@ -14,10 +14,11 @@ use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
use servo::compositing::windowing::{
AnimationState, EmbedderCoordinates, EmbedderEvent, MouseWindowEvent, WindowMethods,
};
use servo::config::opts::Opts;
use servo::embedder_traits::Cursor;
use servo::keyboard_types::{Key, KeyState, KeyboardEvent};
use servo::script_traits::{TouchEventType, WheelDelta, WheelMode};
use servo::servo_config::{opts, pref};
use servo::servo_config::pref;
use servo::servo_geometry::DeviceIndependentPixel;
use servo::webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixel};
use servo::webrender_api::ScrollLocation;
@ -57,14 +58,13 @@ pub struct Window {
impl Window {
pub fn new(
opts: &Opts,
rendering_context: &RenderingContext,
window_size: Size2D<u32, DeviceIndependentPixel>,
event_loop: &ActiveEventLoop,
no_native_titlebar: bool,
device_pixel_ratio_override: Option<f32>,
) -> Window {
let opts = opts::get();
// If there's no chrome, start off with the window invisible. It will be set to visible in
// `load_end()`. This avoids an ugly flash of unstyled content (especially important since
// unstyled content is white and chrome often has a transparent background). See issue
@ -625,13 +625,13 @@ impl webxr::glwindow::GlWindow for XRWindow {
}
fn get_mode(&self) -> webxr::glwindow::GlWindowMode {
if pref!(dom.webxr.glwindow.red_cyan) {
if pref!(dom_webxr_glwindow_red_cyan) {
webxr::glwindow::GlWindowMode::StereoRedCyan
} else if pref!(dom.webxr.glwindow.left_right) {
} else if pref!(dom_webxr_glwindow_left_right) {
webxr::glwindow::GlWindowMode::StereoLeftRight
} else if pref!(dom.webxr.glwindow.spherical) {
} else if pref!(dom_webxr_glwindow_spherical) {
webxr::glwindow::GlWindowMode::Spherical
} else if pref!(dom.webxr.glwindow.cubemap) {
} else if pref!(dom_webxr_glwindow_cubemap) {
webxr::glwindow::GlWindowMode::Cubemap
} else {
webxr::glwindow::GlWindowMode::Blit

View file

@ -13,7 +13,6 @@ use euclid::{Box2D, Length, Point2D, Scale, Size2D};
use servo::compositing::windowing::{
AnimationState, EmbedderCoordinates, EmbedderEvent, WindowMethods,
};
use servo::config::opts;
use servo::servo_geometry::DeviceIndependentPixel;
use servo::webrender_api::units::{DeviceIntSize, DevicePixel};
@ -34,6 +33,7 @@ impl Window {
pub fn new(
size: Size2D<u32, DeviceIndependentPixel>,
device_pixel_ratio_override: Option<f32>,
screen_size_override: Option<Size2D<u32, DeviceIndependentPixel>>,
) -> Rc<dyn WindowPortsMethods> {
let device_pixel_ratio_override: Option<Scale<f32, DeviceIndependentPixel, DevicePixel>> =
device_pixel_ratio_override.map(Scale::new);
@ -43,7 +43,7 @@ impl Window {
let inner_size = Cell::new((size.to_f32() * hidpi_factor).to_i32());
let window_rect = Box2D::from_origin_and_size(Point2D::zero(), size);
let screen_size = opts::get().screen_size_override.map_or_else(
let screen_size = screen_size_override.map_or_else(
|| window_rect.size(),
|screen_size_override| screen_size_override.to_i32(),
);

View file

@ -36,6 +36,7 @@ use super::geometry::winit_position_to_euclid_point;
use super::webview::{LoadStatus, WebViewManager};
use super::window_trait::WindowPortsMethods;
use crate::parser::location_bar_input_to_url;
use crate::prefs::ServoShellPreferences;
pub struct Minibrowser {
pub context: EguiGlow,
@ -497,18 +498,20 @@ impl Minibrowser {
&self,
browser: &WebViewManager<dyn WindowPortsMethods>,
app_event_queue: &mut Vec<EmbedderEvent>,
preferences: &ServoShellPreferences,
) {
for event in self.event_queue.borrow_mut().drain(..) {
let browser_id = browser.focused_webview_id().unwrap();
match event {
MinibrowserEvent::Go => {
let location = self.location.borrow();
if let Some(url) = location_bar_input_to_url(&location.clone()) {
app_event_queue.push(EmbedderEvent::LoadUrl(browser_id, url));
} else {
let Some(url) =
location_bar_input_to_url(&location.clone(), &preferences.searchpage)
else {
warn!("failed to parse location");
break;
}
};
app_event_queue.push(EmbedderEvent::LoadUrl(browser_id, url));
},
MinibrowserEvent::Back => {
app_event_queue.push(EmbedderEvent::Navigation(

View file

@ -19,6 +19,7 @@ use keyboard_types::{Key, KeyboardEvent, Modifiers, ShortcutMatcher};
use log::{debug, error, info, trace, warn};
use servo::base::id::TopLevelBrowsingContextId as WebViewId;
use servo::compositing::windowing::{EmbedderEvent, WebRenderDebugOption};
use servo::config::opts::Opts;
use servo::embedder_traits::{
CompositorEventVariant, ContextMenuResult, DualRumbleEffectParams, EmbedderMsg, FilterPattern,
GamepadHapticEffectType, PermissionPrompt, PermissionRequest, PromptCredentialsInput,
@ -29,7 +30,6 @@ use servo::script_traits::{
GamepadEvent, GamepadIndex, GamepadInputBounds, GamepadSupportedHapticEffects,
GamepadUpdateType, TouchEventType, TraversalDirection,
};
use servo::servo_config::opts;
use servo::servo_url::ServoUrl;
use servo::webrender_api::units::DeviceRect;
use servo::webrender_api::ScrollLocation;
@ -630,6 +630,7 @@ where
/// Returns true if the caller needs to manually present a new frame.
pub fn handle_servo_events(
&mut self,
opts: &Opts,
events: Drain<'_, (Option<WebViewId>, EmbedderMsg)>,
) -> ServoEventResponse {
let mut need_present = self.load_status() != LoadStatus::LoadComplete;
@ -685,7 +686,7 @@ where
self.window.request_inner_size(size);
},
EmbedderMsg::Prompt(definition, origin) => {
let res = if opts::get().headless {
let res = if opts.headless {
match definition {
PromptDefinition::Alert(_message, sender) => sender.send(()),
PromptDefinition::YesNo(_message, sender) => {
@ -927,10 +928,7 @@ where
};
},
EmbedderMsg::SelectFiles(patterns, multiple_files, sender) => {
let res = match (
opts::get().headless,
get_selected_files(patterns, multiple_files),
) {
let res = match (opts.headless, get_selected_files(patterns, multiple_files)) {
(true, _) | (false, None) => sender.send(None),
(false, Some(files)) => sender.send(Some(files)),
};
@ -941,8 +939,10 @@ where
};
},
EmbedderMsg::PromptPermission(prompt, sender) => {
let permission_state = prompt_user(prompt);
let _ = sender.send(permission_state);
let _ = sender.send(match opts.headless {
true => PermissionRequest::Denied,
false => prompt_user(prompt),
});
},
EmbedderMsg::ShowIME(_kind, _text, _multiline, _rect) => {
debug!("ShowIME received");
@ -1007,10 +1007,6 @@ where
#[cfg(target_os = "linux")]
fn prompt_user(prompt: PermissionPrompt) -> PermissionRequest {
if opts::get().headless {
return PermissionRequest::Denied;
}
let message = match prompt {
PermissionPrompt::Request(permission_name) => {
format!("Do you want to grant permission for {:?}?", permission_name)

View file

@ -9,7 +9,6 @@ use std::rc::Rc;
use euclid::{Length, Scale};
use servo::compositing::windowing::{EmbedderEvent, WindowMethods};
use servo::config::opts;
use servo::embedder_traits::Cursor;
use servo::servo_geometry::DeviceIndependentPixel;
use servo::webrender_api::units::{DeviceIntPoint, DeviceIntSize, DevicePixel};
@ -22,10 +21,7 @@ pub trait WindowPortsMethods: WindowMethods {
fn id(&self) -> winit::window::WindowId;
fn hidpi_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
self.device_pixel_ratio_override()
.unwrap_or_else(|| match opts::get().output_file {
Some(_) => Scale::new(1.0),
None => self.device_hidpi_factor(),
})
.unwrap_or_else(|| self.device_hidpi_factor())
}
fn device_hidpi_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel>;
fn device_pixel_ratio_override(