mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
* winit: add minibrowser feature that depends on egui{,-winit} * winit: carve out some space at the top of headed windows * winit: minimal toolbar and egui/winit integration (but no painting) * winit: try to paint with egui_glow (doesn’t work yet) * winit: add comment about toolbar size * Add framebuffer object, set it as glow's target * compositing: clear only the viewport, not the whole framebuffer * plumb the actual size of the egui toolbar to webrender * fix formatting * winit: fix crash when fbo is zero * winit: don’t bother binding the framebuffer object * winit: remove unsafe and get toolbar_height * winit: location field should reflect the current top-level url * [NFC] winit: move Minibrowser out of App::run * winit: clean up toolbar height code * winit: make App own the Minibrowser if any * winit: make the go button work * winit:make the location field reflect the current top-level url * winit: allow enabling minibrowser from command line * winit: tell compositor to repaint WR and flush when we repaint * winit: fix bug where location field edits would get overridden * winit: borrow the minibrowser once in App::handle_events * winit: address todo about viewport origin coordinates * winit: fix some minor problems with comments and errors * winit: update location field once per HistoryChanged event * winit: rename Window::set_toolbar_size to set_toolbar_height * winit: take toolbar height into account in hit testing * winit: pass egui only relevant CursorMoved events * winit: scratch that, coalesce minibrowser updates instead * ensure both minibrowser and WR are repainted on every frame * compositing: only skip framebuffer clear in external present mode * winit: destroy egui glow Painter when shutting down * winit: clean up and fix license lint * fix duplicate versions lint by downgrading bytemuck_derive was egui_glow ^0.22.0 (0.22.0) → egui/bytemuck ^0.22.0 (0.22.0) → epaint/bytemuck ^0.22.0 (0.22.0) → bytemuck ^1.7.2 (1.13.1) → bytemuck_derive ^1.4 (1.4.1) → syn ^2.0.1 (2.0.28) now lock has bytemuck_derive 1.4.0 → syn ^1.0.99 (1.0.103) * fix duplicate versions lint by disabling egui-winit/links (we don’t need support for hyperlinks in our use of egui) * squelch duplicate versions lint by excluding clipboard-win * winit: fix compile warnings * winit: make gleam an optional dependency under /minibrowser * winit: remove cargo feature, since it’s not really optional * winit: extract Minibrowser and related code to separate module * winit: remove unnecessary trailing comma * winit: simplify the ServoUrl serialisation optimisation --------- Co-authored-by: atbrakhi <atbrakhi@igalia.com>
357 lines
15 KiB
Rust
357 lines
15 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 crate::browser::Browser;
|
|
use crate::embedder::EmbedderCallbacks;
|
|
use crate::events_loop::{EventsLoop, WakerEvent};
|
|
use crate::minibrowser::Minibrowser;
|
|
use crate::window_trait::WindowPortsMethods;
|
|
use crate::{headed_window, headless_window};
|
|
use gleam::gl;
|
|
use winit::window::WindowId;
|
|
use winit::event_loop::EventLoopWindowTarget;
|
|
use servo::compositing::windowing::EmbedderEvent;
|
|
use servo::config::opts::{self, parse_url_or_filename};
|
|
use servo::servo_config::pref;
|
|
use servo::servo_url::ServoUrl;
|
|
use servo::Servo;
|
|
use std::cell::{Cell, RefCell, RefMut};
|
|
use std::collections::HashMap;
|
|
use std::env;
|
|
|
|
use std::rc::Rc;
|
|
use surfman::GLApi;
|
|
use webxr::glwindow::GlWindowDiscovery;
|
|
|
|
pub struct App {
|
|
servo: Option<Servo<dyn WindowPortsMethods>>,
|
|
browser: RefCell<Browser<dyn WindowPortsMethods>>,
|
|
event_queue: RefCell<Vec<EmbedderEvent>>,
|
|
suspended: Cell<bool>,
|
|
windows: HashMap<WindowId, Rc<dyn WindowPortsMethods>>,
|
|
minibrowser: Option<RefCell<Minibrowser>>,
|
|
}
|
|
|
|
/// Action to be taken by the caller of [`App::handle_events`].
|
|
enum PumpResult {
|
|
Shutdown,
|
|
Present,
|
|
}
|
|
|
|
impl App {
|
|
pub fn run(
|
|
no_native_titlebar: bool,
|
|
device_pixels_per_px: Option<f32>,
|
|
user_agent: Option<String>,
|
|
) {
|
|
let events_loop = EventsLoop::new(opts::get().headless, opts::get().output_file.is_some());
|
|
|
|
// Implements window methods, used by compositor.
|
|
let window = if opts::get().headless {
|
|
headless_window::Window::new(opts::get().initial_window_size, device_pixels_per_px)
|
|
} else {
|
|
Rc::new(headed_window::Window::new(
|
|
opts::get().initial_window_size,
|
|
&events_loop,
|
|
no_native_titlebar,
|
|
device_pixels_per_px,
|
|
))
|
|
};
|
|
|
|
// Handle browser state.
|
|
let browser = Browser::new(window.clone());
|
|
|
|
let mut app = App {
|
|
event_queue: RefCell::new(vec![]),
|
|
browser: RefCell::new(browser),
|
|
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 webrender_surfman = window.webrender_surfman();
|
|
let webrender_gl = match webrender_surfman.connection().gl_api() {
|
|
GLApi::GL => unsafe { gl::GlFns::load_with(|s| webrender_surfman.get_proc_address(s)) },
|
|
GLApi::GLES => unsafe {
|
|
gl::GlesFns::load_with(|s| webrender_surfman.get_proc_address(s))
|
|
},
|
|
};
|
|
webrender_surfman.make_gl_context_current().unwrap();
|
|
debug_assert_eq!(webrender_gl.get_error(), gleam::gl::NO_ERROR);
|
|
|
|
// Set up egui context for minibrowser ui
|
|
// Adapted from https://github.com/emilk/egui/blob/9478e50d012c5138551c38cbee16b07bc1fcf283/crates/egui_glow/examples/pure_glow.rs
|
|
app.minibrowser = Some(Minibrowser::new(&webrender_surfman, &events_loop).into());
|
|
}
|
|
|
|
if let Some(mut minibrowser) = app.minibrowser() {
|
|
minibrowser.update(window.winit_window().unwrap());
|
|
window.set_toolbar_height(minibrowser.toolbar_height.get());
|
|
}
|
|
|
|
let ev_waker = events_loop.create_event_loop_waker();
|
|
events_loop.run_forever(move |e, w, control_flow| {
|
|
match e {
|
|
winit::event::Event::NewEvents(winit::event::StartCause::Init) => {
|
|
let surfman = window.webrender_surfman();
|
|
|
|
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 servo_data = Servo::new(embedder, window.clone(), user_agent.clone());
|
|
let mut servo = servo_data.servo;
|
|
|
|
// If we have a minibrowser, ask the compositor to notify us when a new frame
|
|
// is ready to present, so that we can paint the minibrowser then present.
|
|
servo.set_external_present(app.minibrowser.is_some());
|
|
|
|
servo.handle_events(vec![EmbedderEvent::NewBrowser(get_default_url(), servo_data.browser_id)]);
|
|
servo.setup_logging();
|
|
|
|
app.windows.insert(window.id(), window.clone());
|
|
app.servo = Some(servo);
|
|
}
|
|
|
|
// TODO does windows still need this workaround?
|
|
// https://github.com/emilk/egui/blob/9478e50d012c5138551c38cbee16b07bc1fcf283/crates/egui_glow/examples/pure_glow.rs#L203
|
|
// winit::event::Event::RedrawEventsCleared => todo!(),
|
|
winit::event::Event::RedrawRequested(_) => {
|
|
if let Some(mut minibrowser) = app.minibrowser() {
|
|
minibrowser.update(window.winit_window().unwrap());
|
|
|
|
// Tell Servo to repaint, which will in turn allow us to repaint the
|
|
// minibrowser and present a complete frame without partial updates.
|
|
app.event_queue.borrow_mut().push(EmbedderEvent::Refresh);
|
|
}
|
|
}
|
|
|
|
_ => {}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Handle the event
|
|
let mut consumed = false;
|
|
if let Some(mut minibrowser) = app.minibrowser() {
|
|
if let winit::event::Event::WindowEvent { ref event, .. } = e {
|
|
let response = minibrowser.context.on_event(&event);
|
|
if response.repaint {
|
|
// Request a redraw event that will in turn trigger a minibrowser update.
|
|
// This allows us to coalesce minibrowser updates across multiple events.
|
|
window.winit_window().unwrap().request_redraw();
|
|
}
|
|
|
|
// TODO how do we handle the tab key? (see doc for consumed)
|
|
consumed = response.consumed;
|
|
}
|
|
}
|
|
if !consumed {
|
|
app.queue_embedder_events_for_winit_event(e);
|
|
}
|
|
|
|
let animating = app.is_animating();
|
|
|
|
// Block until the window gets an event
|
|
if !animating || app.suspended.get() {
|
|
*control_flow = winit::event_loop::ControlFlow::Wait;
|
|
} else {
|
|
*control_flow = winit::event_loop::ControlFlow::Poll;
|
|
}
|
|
|
|
// Consume and handle any events from the Minibrowser.
|
|
if let Some(mut minibrowser) = app.minibrowser() {
|
|
let browser = &mut app.browser.borrow_mut();
|
|
let app_event_queue = &mut app.event_queue.borrow_mut();
|
|
minibrowser.queue_embedder_events_for_minibrowser_events(browser, app_event_queue);
|
|
if minibrowser.update_location_in_toolbar(browser) {
|
|
// 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());
|
|
}
|
|
}
|
|
|
|
match app.handle_events() {
|
|
Some(PumpResult::Shutdown) => {
|
|
*control_flow = winit::event_loop::ControlFlow::Exit;
|
|
app.servo.take().unwrap().deinit();
|
|
if let Some(mut minibrowser) = app.minibrowser() {
|
|
minibrowser.context.destroy();
|
|
}
|
|
},
|
|
Some(PumpResult::Present) => {
|
|
if let Some(mut minibrowser) = app.minibrowser() {
|
|
minibrowser.paint(window.winit_window().unwrap());
|
|
}
|
|
app.servo.as_mut().unwrap().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 { .. } => {},
|
|
|
|
winit::event::Event::RedrawRequested(_) => {
|
|
self.event_queue.borrow_mut().push(EmbedderEvent::Idle);
|
|
},
|
|
|
|
// 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) => {
|
|
window.queue_embedder_events_for_winit_event(event);
|
|
},
|
|
}
|
|
},
|
|
|
|
winit::event::Event::LoopDestroyed |
|
|
winit::event::Event::NewEvents(..) |
|
|
winit::event::Event::MainEventsCleared |
|
|
winit::event::Event::RedrawEventsCleared => {},
|
|
}
|
|
}
|
|
|
|
/// 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 Browser queue, and from the Browser 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 Browser can handle them.
|
|
fn handle_events(&mut self) -> Option<PumpResult> {
|
|
let mut browser = self.browser.borrow_mut();
|
|
|
|
// FIXME:
|
|
// As of now, we support only one browser (self.browser)
|
|
// but have multiple windows (dom.webxr.glwindow). We forward
|
|
// the events of all the windows combined to that single
|
|
// browser instance. Pressing the "a" key on the glwindow
|
|
// will send a key event to the servo window.
|
|
|
|
// Take any outstanding embedder events from the App and its Windows.
|
|
let mut embedder_events = self.get_events();
|
|
for (_win_id, window) in &self.windows {
|
|
embedder_events.extend(window.get_events());
|
|
}
|
|
|
|
// Catch some keyboard events, and push the rest onto the Browser event queue.
|
|
browser.handle_window_events(embedder_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;
|
|
loop {
|
|
// Consume and handle those embedder messages.
|
|
need_present |= browser.handle_servo_events(embedder_messages);
|
|
|
|
// Route embedder events from the Browser 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(browser.get_events());
|
|
if browser.shutdown_requested() {
|
|
return Some(PumpResult::Shutdown);
|
|
}
|
|
|
|
// Take any new embedder messages from Servo itself.
|
|
embedder_messages = self.servo.as_mut().unwrap().get_events();
|
|
if embedder_messages.is_empty() {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if need_resize {
|
|
self.servo.as_mut().unwrap().repaint_synchronously();
|
|
need_present = true;
|
|
}
|
|
if need_present {
|
|
Some(PumpResult::Present)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn minibrowser(&self) -> Option<RefMut<Minibrowser>> {
|
|
self.minibrowser.as_ref().map(|x| x.borrow_mut())
|
|
}
|
|
}
|
|
|
|
fn get_default_url() -> ServoUrl {
|
|
// If the url is not provided, we fallback to the homepage in prefs,
|
|
// or a blank page in case the homepage is not set either.
|
|
let cwd = env::current_dir().unwrap();
|
|
let cmdline_url = opts::get().url.clone();
|
|
let pref_url = {
|
|
let homepage_url = pref!(shell.homepage);
|
|
parse_url_or_filename(&cwd, &homepage_url).ok()
|
|
};
|
|
let blank_url = ServoUrl::parse("about:blank").ok();
|
|
|
|
cmdline_url.or(pref_url).or(blank_url).unwrap()
|
|
}
|