libservo: Port desktop servoshell to use the new WebView API (#35183)

This removes all uses of `EmbedderEvent` in the desktop servoshell to
use the new `WebView` API -- filling it out when necessary.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Delan Azabani <dazabani@igalia.com>
This commit is contained in:
Martin Robinson 2025-01-28 15:57:57 +01:00 committed by GitHub
parent a1cf0cbf86
commit 9dbc942bee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 826 additions and 798 deletions

View file

@ -20,22 +20,18 @@ use gleam::gl;
use glow::NativeFramebuffer;
use log::{trace, warn};
use servo::base::id::WebViewId;
use servo::compositing::windowing::EmbedderEvent;
use servo::servo_geometry::DeviceIndependentPixel;
use servo::servo_url::ServoUrl;
use servo::webrender_api::units::DevicePixel;
use servo::webrender_traits::SurfmanRenderingContext;
use servo::{TopLevelBrowsingContextId, TraversalDirection};
use servo::Servo;
use winit::event::{ElementState, MouseButton, WindowEvent};
use winit::event_loop::ActiveEventLoop;
use winit::window::Window;
use super::egui_glue::EguiGlow;
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;
use super::webview::{LoadStatus, WebView, WebViewManager};
pub struct Minibrowser {
pub context: EguiGlow,
@ -60,11 +56,12 @@ pub struct Minibrowser {
pub enum MinibrowserEvent {
/// Go button clicked.
Go,
Go(String),
Back,
Forward,
Reload,
NewWebView,
CloseWebView(WebViewId),
}
fn truncate_with_ellipsis(input: &str, max_length: usize) -> String {
@ -116,6 +113,10 @@ impl Minibrowser {
}
}
pub(crate) fn take_events(&self) -> Vec<MinibrowserEvent> {
self.event_queue.take()
}
/// Preprocess the given [winit::event::WindowEvent], returning unconsumed for mouse events in
/// the Servo browser rect. This is needed because the CentralPanel we create for our webview
/// would otherwise make egui report events in that area as consumed.
@ -169,15 +170,15 @@ impl Minibrowser {
.min_size(Vec2 { x: 20.0, y: 20.0 })
}
/// Draws a browser tab, checking for clicks and returns an appropriate [EmbedderEvent]
/// Draws a browser tab, checking for clicks and queues appropriate `MinibrowserEvent`s.
/// Using a custom widget here would've been nice, but it doesn't seem as though egui
/// supports that, so we arrange multiple Widgets in a way that they look connected.
fn browser_tab(
ui: &mut egui::Ui,
label: &str,
selected: bool,
webview_id: TopLevelBrowsingContextId,
) -> Option<EmbedderEvent> {
webview: &WebView,
event_queue: &mut Vec<MinibrowserEvent>,
) {
let old_item_spacing = ui.spacing().item_spacing;
let old_visuals = ui.visuals().clone();
let active_bg_color = old_visuals.widgets.active.weak_bg_fill;
@ -213,6 +214,7 @@ impl Minibrowser {
visuals.widgets.hovered.rounding = rounding;
visuals.widgets.inactive.rounding = rounding;
let selected = webview.focused;
let tab = ui.add(SelectableLabel::new(
selected,
truncate_with_ellipsis(label, 20),
@ -242,11 +244,9 @@ impl Minibrowser {
let close_button = ui.add(egui::Button::new("X").fill(fill_color));
*ui.visuals_mut() = old_visuals;
if close_button.clicked() || close_button.middle_clicked() || tab.middle_clicked() {
Some(EmbedderEvent::CloseWebView(webview_id))
event_queue.push(MinibrowserEvent::CloseWebView(webview.servo_webview.id()))
} else if !selected && tab.clicked() {
Some(EmbedderEvent::FocusWebView(webview_id))
} else {
None
webview.servo_webview.focus();
}
}
@ -256,8 +256,8 @@ impl Minibrowser {
pub fn update(
&mut self,
window: &Window,
webviews: &mut WebViewManager<dyn WindowPortsMethods>,
servo_framebuffer_id: Option<gl::GLuint>,
webviews: &mut WebViewManager,
servo: Option<&mut Servo>,
reason: &'static str,
) {
let now = Instant::now();
@ -277,6 +277,9 @@ impl Minibrowser {
..
} = self;
let widget_fbo = *widget_surface_fbo;
let servo_framebuffer_id = servo
.as_ref()
.and_then(|servo| servo.offscreen_framebuffer_id());
let _duration = context.run(window, |ctx| {
// TODO: While in fullscreen add some way to mitigate the increased phishing risk
// when not displaying the URL bar: https://github.com/servo/servo/issues/32443
@ -342,8 +345,9 @@ impl Minibrowser {
if location_field.lost_focus() &&
ui.input(|i| i.clone().key_pressed(Key::Enter))
{
event_queue.borrow_mut().push(MinibrowserEvent::Go);
location_dirty.set(false);
event_queue
.borrow_mut()
.push(MinibrowserEvent::Go(location.borrow().clone()));
}
},
);
@ -352,26 +356,19 @@ impl Minibrowser {
});
};
let mut embedder_events = vec![];
// A simple Tab header strip
TopBottomPanel::top("tabs").show(ctx, |ui| {
ui.allocate_ui_with_layout(
ui.available_size(),
egui::Layout::left_to_right(egui::Align::Center),
|ui| {
for (webview_id, webview) in webviews.webviews().into_iter() {
for (_, webview) in webviews.webviews().into_iter() {
let label = match (&webview.title, &webview.url) {
(Some(title), _) if !title.is_empty() => title,
(_, Some(url)) => &url.to_string(),
_ => "New Tab",
};
if let Some(event) =
Self::browser_tab(ui, label, webview.focused, webview_id)
{
location_dirty.set(false);
embedder_events.push(event);
}
Self::browser_tab(ui, label, webview, &mut event_queue.borrow_mut());
}
if ui.add(Minibrowser::toolbar_button("+")).clicked() {
event_queue.borrow_mut().push(MinibrowserEvent::NewWebView);
@ -408,8 +405,7 @@ impl Minibrowser {
) * scale;
if rect != webview.rect {
webview.rect = rect;
embedder_events
.push(EmbedderEvent::MoveResizeWebView(focused_webview_id, rect));
webview.servo_webview.move_resize(rect)
}
let min = ui.cursor().min;
let size = ui.available_size();
@ -471,10 +467,6 @@ impl Minibrowser {
});
});
if !embedder_events.is_empty() {
webviews.handle_window_events(embedder_events);
}
*last_update = now;
});
}
@ -491,58 +483,9 @@ impl Minibrowser {
self.context.paint(window);
}
/// Takes any outstanding events from the [Minibrowser], converting them to [EmbedderEvent] and
/// routing those to the App event queue.
pub fn queue_embedder_events_for_minibrowser_events(
&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();
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(
browser_id,
TraversalDirection::Back(1),
));
},
MinibrowserEvent::Forward => {
app_event_queue.push(EmbedderEvent::Navigation(
browser_id,
TraversalDirection::Forward(1),
));
},
MinibrowserEvent::Reload => {
let browser_id = browser.focused_webview_id().unwrap();
app_event_queue.push(EmbedderEvent::Reload(browser_id));
},
MinibrowserEvent::NewWebView => {
self.location_dirty.set(false);
let url = ServoUrl::parse("servo:newtab").unwrap();
app_event_queue.push(EmbedderEvent::NewWebView(url, WebViewId::new()));
},
}
}
}
/// Updates the location field from the given [WebViewManager], unless the user has started
/// editing it without clicking Go, returning true iff it has changed (needing an egui update).
pub fn update_location_in_toolbar(
&mut self,
browser: &mut WebViewManager<dyn WindowPortsMethods>,
) -> bool {
pub fn update_location_in_toolbar(&mut self, browser: &mut WebViewManager) -> bool {
// User edited without clicking Go?
if self.location_dirty.get() {
return false;
@ -557,21 +500,19 @@ impl Minibrowser {
}
}
pub fn update_location_dirty(&self, dirty: bool) {
self.location_dirty.set(dirty);
}
/// Updates the spinner from the given [WebViewManager], returning true iff it has changed
/// (needing an egui update).
pub fn update_spinner_in_toolbar(
&mut self,
browser: &mut WebViewManager<dyn WindowPortsMethods>,
) -> bool {
pub fn update_spinner_in_toolbar(&mut self, browser: &mut WebViewManager) -> bool {
let need_update = browser.load_status() != self.load_status;
self.load_status = browser.load_status();
need_update
}
pub fn update_status_text(
&mut self,
browser: &mut WebViewManager<dyn WindowPortsMethods>,
) -> bool {
pub fn update_status_text(&mut self, browser: &mut WebViewManager) -> bool {
let need_update = browser.status_text() != self.status_text;
self.status_text = browser.status_text();
need_update
@ -579,10 +520,7 @@ impl Minibrowser {
/// Updates all fields taken from the given [WebViewManager], such as the location field.
/// Returns true iff the egui needs an update.
pub fn update_webview_data(
&mut self,
browser: &mut WebViewManager<dyn WindowPortsMethods>,
) -> bool {
pub fn update_webview_data(&mut self, browser: &mut WebViewManager) -> bool {
// Note: We must use the "bitwise OR" (|) operator here instead of "logical OR" (||)
// because logical OR would short-circuit if any of the functions return true.
// We want to ensure that all functions are called. The "bitwise OR" operator