Split servoshell into Desktop and common part (#32457)

* servoshell: Move desktop files

Move files related to winit into a desktop module.
This is a preparation to merge the android and ohos apps into
servoshell.

* servoshell: Format imports

* servoshell: Move panic hook into separate file

* servoshell: Move desktop main

* Consider ohos as not desktop

* servoshell: Adjust dependencies for shared code

* servoshell: Remove native-bluetooth from default features

There currently is no good way to have target specific default features.

* Rename desktop_main.rs to cli.rs

* Remove todo
This commit is contained in:
Jonathan Schwender 2024-06-14 08:26:35 +02:00 committed by GitHub
parent bae9f6d844
commit ff4cd4af96
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 233 additions and 179 deletions

View file

@ -0,0 +1,477 @@
/* 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::cell::{Cell, RefCell, RefMut};
use std::collections::HashMap;
use std::rc::Rc;
use std::time::Instant;
use std::{env, fs};
use gleam::gl;
use log::{info, trace, warn};
use servo::compositing::windowing::EmbedderEvent;
use servo::compositing::CompositeTarget;
use servo::config::{opts, set_pref};
use servo::servo_config::pref;
use servo::Servo;
use surfman::GLApi;
use webxr::glwindow::GlWindowDiscovery;
use winit::event::WindowEvent;
use winit::event_loop::EventLoopWindowTarget;
use winit::window::WindowId;
use super::events_loop::{EventsLoop, WakerEvent};
use super::minibrowser::Minibrowser;
use super::webview::WebViewManager;
use super::{headed_window, headless_window};
use crate::desktop::embedder::EmbedderCallbacks;
use crate::desktop::tracing::trace_winit_event;
use crate::desktop::window_trait::WindowPortsMethods;
use crate::parser::get_default_url;
pub struct App {
servo: Option<Servo<dyn WindowPortsMethods>>,
webviews: RefCell<WebViewManager<dyn WindowPortsMethods>>,
event_queue: RefCell<Vec<EmbedderEvent>>,
suspended: Cell<bool>,
windows: HashMap<WindowId, Rc<dyn WindowPortsMethods>>,
minibrowser: Option<RefCell<Minibrowser>>,
}
enum Present {
Immediate,
Deferred,
None,
}
/// Action to be taken by the caller of [`App::handle_events`].
enum PumpResult {
/// The caller should shut down Servo and its related context.
Shutdown,
Continue {
update: bool,
present: Present,
},
}
impl App {
pub fn run(
no_native_titlebar: bool,
device_pixel_ratio_override: Option<f32>,
user_agent: Option<String>,
url: Option<String>,
) {
let events_loop = EventsLoop::new(opts::get().headless, opts::get().output_file.is_some())
.expect("Failed to create events loop");
// Implements window methods, used by compositor.
let window = if opts::get().headless {
// GL video rendering is not supported on headless windows.
set_pref!(media.glvideo.enabled, false);
headless_window::Window::new(
opts::get().initial_window_size,
device_pixel_ratio_override,
)
} else {
Rc::new(headed_window::Window::new(
opts::get().initial_window_size,
&events_loop,
no_native_titlebar,
device_pixel_ratio_override,
))
};
// Handle browser state.
let webviews = WebViewManager::new(window.clone());
let initial_url = get_default_url(url.as_deref(), env::current_dir().unwrap(), |path| {
fs::metadata(path).is_ok()
});
let mut app = App {
event_queue: RefCell::new(vec![]),
webviews: RefCell::new(webviews),
servo: None,
suspended: Cell::new(false),
windows: HashMap::new(),
minibrowser: None,
};
if opts::get().minibrowser && window.winit_window().is_some() {
// Make sure the gl context is made current.
let rendering_context = window.rendering_context();
let webrender_gl = match rendering_context.connection().gl_api() {
GLApi::GL => unsafe {
gl::GlFns::load_with(|s| rendering_context.get_proc_address(s))
},
GLApi::GLES => unsafe {
gl::GlesFns::load_with(|s| rendering_context.get_proc_address(s))
},
};
rendering_context.make_gl_context_current().unwrap();
debug_assert_eq!(webrender_gl.get_error(), gleam::gl::NO_ERROR);
app.minibrowser = Some(
Minibrowser::new(&rendering_context, &events_loop, initial_url.clone()).into(),
);
}
if let Some(mut minibrowser) = app.minibrowser() {
// Servo is not yet initialised, so there is no `servo_framebuffer_id`.
minibrowser.update(
window.winit_window().unwrap(),
&mut app.webviews.borrow_mut(),
None,
"init",
);
window.set_toolbar_height(minibrowser.toolbar_height);
}
let t_start = Instant::now();
let mut t = t_start;
let ev_waker = events_loop.create_event_loop_waker();
events_loop.run_forever(move |event, w, control_flow| {
let now = Instant::now();
trace_winit_event!(event, "@{:?} (+{:?}) {event:?}", now - t_start, now - t);
t = now;
if let winit::event::Event::NewEvents(winit::event::StartCause::Init) = event {
let surfman = window.rendering_context();
let xr_discovery = if pref!(dom.webxr.glwindow.enabled) && !opts::get().headless {
let window = window.clone();
// This should be safe because run_forever does, in fact,
// run forever. The event loop window target doesn't get
// moved, and does outlast this closure, and we won't
// ever try to make use of it once shutdown begins and
// it stops being valid.
let w = unsafe {
std::mem::transmute::<
&EventLoopWindowTarget<WakerEvent>,
&'static EventLoopWindowTarget<WakerEvent>,
>(w.unwrap())
};
let factory = Box::new(move || Ok(window.new_glwindow(w)));
Some(GlWindowDiscovery::new(
surfman.connection(),
surfman.adapter(),
surfman.context_attributes(),
factory,
))
} else {
None
};
let window = window.clone();
// Implements embedder methods, used by libservo and constellation.
let embedder = Box::new(EmbedderCallbacks::new(ev_waker.clone(), xr_discovery));
let composite_target = if app.minibrowser.is_some() {
CompositeTarget::Fbo
} else {
CompositeTarget::Window
};
let servo_data = Servo::new(
embedder,
window.clone(),
user_agent.clone(),
composite_target,
);
let mut servo = servo_data.servo;
servo.handle_events(vec![EmbedderEvent::NewWebView(
initial_url.to_owned(),
servo_data.browser_id,
)]);
servo.setup_logging();
app.windows.insert(window.id(), window.clone());
app.servo = Some(servo);
}
// If self.servo is None here, it means that we're in the process of shutting down,
// let's ignore events.
if app.servo.is_none() {
return;
}
if let winit::event::Event::WindowEvent {
window_id: _,
event: winit::event::WindowEvent::RedrawRequested,
} = event
{
// We need to redraw the window for some reason.
trace!("RedrawRequested");
// WARNING: do not defer painting or presenting to some later tick of the event
// loop or servoshell may become unresponsive! (servo#30312)
if let Some(mut minibrowser) = app.minibrowser() {
minibrowser.update(
window.winit_window().unwrap(),
&mut app.webviews.borrow_mut(),
app.servo.as_ref().unwrap().offscreen_framebuffer_id(),
"RedrawRequested",
);
minibrowser.paint(window.winit_window().unwrap());
}
app.servo.as_mut().unwrap().present();
}
// Handle the event
let mut consumed = false;
if let Some(mut minibrowser) = app.minibrowser() {
match event {
winit::event::Event::WindowEvent {
event: WindowEvent::ScaleFactorChanged { scale_factor, .. },
..
} => {
// Intercept any ScaleFactorChanged events away from EguiGlow::on_window_event, so
// we can use our own logic for calculating the scale factor and set eguis
// scale factor to that value manually.
let effective_scale_factor = window.hidpi_factor().get();
info!(
"window scale factor changed to {}, setting scale factor to {}",
scale_factor, effective_scale_factor
);
minibrowser
.context
.egui_ctx
.set_pixels_per_point(effective_scale_factor);
// Request a winit redraw event, so we can recomposite, update and paint
// the minibrowser, and present the new frame.
window.winit_window().unwrap().request_redraw();
},
winit::event::Event::WindowEvent {
ref event,
window_id: _,
} => {
let response =
minibrowser.on_window_event(window.winit_window().unwrap(), event);
// Update minibrowser if there's resize event to sync up with window.
if let WindowEvent::Resized(_) = event {
minibrowser.update(
window.winit_window().unwrap(),
&mut app.webviews.borrow_mut(),
app.servo.as_ref().unwrap().offscreen_framebuffer_id(),
"Sync WebView size with Window Resize event",
);
}
if response.repaint {
// Request a winit redraw event, so we can recomposite, update and paint
// the minibrowser, and present the new frame.
window.winit_window().unwrap().request_redraw();
}
// TODO how do we handle the tab key? (see doc for consumed)
// Note that servo doesnt yet support tabbing through links and inputs
consumed = response.consumed;
},
_ => {},
}
}
if !consumed {
app.queue_embedder_events_for_winit_event(event);
}
let animating = app.is_animating();
// Block until the window gets an event
if !animating || app.suspended.get() {
control_flow.set_wait();
} else {
control_flow.set_poll();
}
// Consume and handle any events from the Minibrowser.
if let Some(minibrowser) = app.minibrowser() {
let webviews = &mut app.webviews.borrow_mut();
let app_event_queue = &mut app.event_queue.borrow_mut();
minibrowser.queue_embedder_events_for_minibrowser_events(webviews, app_event_queue);
}
match app.handle_events() {
PumpResult::Shutdown => {
control_flow.set_exit();
app.servo.take().unwrap().deinit();
if let Some(mut minibrowser) = app.minibrowser() {
minibrowser.context.destroy();
}
},
PumpResult::Continue { update, present } => {
if update {
if let Some(mut minibrowser) = app.minibrowser() {
let webviews = &mut app.webviews.borrow_mut();
if minibrowser.update_webview_data(webviews) {
// Update the minibrowser immediately. While we could update by requesting a
// redraw, doing so would delay the location update by two frames.
minibrowser.update(
window.winit_window().unwrap(),
webviews,
app.servo.as_ref().unwrap().offscreen_framebuffer_id(),
"update_location_in_toolbar",
);
}
}
}
match present {
Present::Immediate => {
// The window was resized.
trace!("PumpResult::Present::Immediate");
// If we had resized any of the viewports in response to this, we would need to
// call Servo::repaint_synchronously. At the moment we dont, so there wont be
// any paint scheduled, and calling it would hang the compositor forever.
if let Some(mut minibrowser) = app.minibrowser() {
minibrowser.update(
window.winit_window().unwrap(),
&mut app.webviews.borrow_mut(),
app.servo.as_ref().unwrap().offscreen_framebuffer_id(),
"PumpResult::Present::Immediate",
);
minibrowser.paint(window.winit_window().unwrap());
}
app.servo.as_mut().unwrap().present();
},
Present::Deferred => {
// The compositor has painted to this frame.
trace!("PumpResult::Present::Deferred");
// Request a winit redraw event, so we can paint the minibrowser and present.
// Otherwise, it's in headless mode and we present directly.
if let Some(window) = window.winit_window() {
window.request_redraw();
} else {
app.servo.as_mut().unwrap().present();
}
},
Present::None => {},
}
},
}
});
}
fn is_animating(&self) -> bool {
self.windows.iter().any(|(_, window)| window.is_animating())
}
fn get_events(&self) -> Vec<EmbedderEvent> {
std::mem::take(&mut *self.event_queue.borrow_mut())
}
/// Processes the given winit Event, possibly converting it to an [EmbedderEvent] and
/// routing that to the App or relevant Window event queues.
fn queue_embedder_events_for_winit_event(&self, event: winit::event::Event<WakerEvent>) {
match event {
// App level events
winit::event::Event::Suspended => {
self.suspended.set(true);
},
winit::event::Event::Resumed => {
self.suspended.set(false);
self.event_queue.borrow_mut().push(EmbedderEvent::Idle);
},
winit::event::Event::UserEvent(_) => {
self.event_queue.borrow_mut().push(EmbedderEvent::Idle);
},
winit::event::Event::DeviceEvent { .. } => {},
// Window level events
winit::event::Event::WindowEvent {
window_id, event, ..
} => match self.windows.get(&window_id) {
None => {
warn!("Got an event from unknown window");
},
Some(window) => {
if event == winit::event::WindowEvent::RedrawRequested {
self.event_queue.borrow_mut().push(EmbedderEvent::Idle);
}
window.queue_embedder_events_for_winit_event(event);
},
},
winit::event::Event::LoopExiting |
winit::event::Event::AboutToWait |
winit::event::Event::MemoryWarning |
winit::event::Event::NewEvents(..) => {},
}
}
/// Pumps events and messages between the embedder and Servo, where embedder events flow
/// towards Servo and embedder messages flow away from Servo, and also runs the compositor.
///
/// As the embedder, we push embedder events through our event queues, from the App queue and
/// Window queues to the WebViewManager queue, and from the WebViewManager queue to Servo. We
/// receive and collect embedder messages from the various Servo components, then take them out
/// of the Servo interface so that the WebViewManager can handle them.
fn handle_events(&mut self) -> PumpResult {
let mut webviews = self.webviews.borrow_mut();
// Take any outstanding embedder events from the App and its Windows.
let mut embedder_events = self.get_events();
for window in self.windows.values() {
embedder_events.extend(window.get_events());
}
// Catch some keyboard events, and push the rest onto the WebViewManager event queue.
webviews.handle_window_events(embedder_events);
// If the Gamepad API is enabled, handle gamepad events from GilRs.
// Checking for current_url_string should ensure we'll have a valid browsing context.
if pref!(dom.gamepad.enabled) && webviews.current_url_string().is_some() {
webviews.handle_gamepad_events();
}
// Take any new embedder messages from Servo itself.
let mut embedder_messages = self.servo.as_mut().unwrap().get_events();
let mut need_resize = false;
let mut need_present = false;
let mut need_update = false;
loop {
// Consume and handle those embedder messages.
let servo_event_response = webviews.handle_servo_events(embedder_messages);
need_present |= servo_event_response.need_present;
need_update |= servo_event_response.need_update;
// Route embedder events from the WebViewManager to the relevant Servo components,
// receives and collects embedder messages from various Servo components,
// and runs the compositor.
need_resize |= self
.servo
.as_mut()
.unwrap()
.handle_events(webviews.get_events());
if webviews.shutdown_requested() {
return PumpResult::Shutdown;
}
// Take any new embedder messages from Servo itself.
embedder_messages = self.servo.as_mut().unwrap().get_events();
if embedder_messages.len() == 0 {
break;
}
}
let present = if need_resize {
Present::Immediate
} else if need_present {
Present::Deferred
} else {
Present::None
};
PumpResult::Continue {
update: need_update,
present,
}
}
fn minibrowser(&self) -> Option<RefMut<Minibrowser>> {
self.minibrowser.as_ref().map(|x| x.borrow_mut())
}
}