libservo: Rework and clarify the rendering model of the WebView (#35522)

Make the rendering model of the `WebView` clearer:

1. `WebViewDelegate::notify_new_frame_ready()` indicates that the
   WebView has become dirty and needs to be repainted.
2. `WebView::paint()` asks Servo to paint the contents of the `WebView`
   into the `RenderingContext`.
3. `RenderingContext::present()` does a buffer swap if the
   `RenderingContext` is actually double-buffered.

This is documented and all in-tree embedders are updated to work with
this new model.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Ngo Iok Ui (Wu Yu Wei) <yuweiwu@pm.me>
Co-authored-by: Mukilan Thiyagarajan <mukilan@igalia.com>
This commit is contained in:
Martin Robinson 2025-02-19 11:35:56 +01:00 committed by GitHub
parent 56840e0a35
commit e5c9a0365d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 129 additions and 160 deletions

View file

@ -58,7 +58,6 @@ pub(crate) enum PumpResult {
Shutdown,
Continue {
need_update: bool,
new_servo_frame: bool,
need_window_redraw: bool,
},
}
@ -195,23 +194,15 @@ impl App {
},
PumpResult::Continue {
need_update: update,
new_servo_frame,
need_window_redraw,
} => {
// A new Servo frame is ready, so swap the buffer on our `RenderingContext`. In headed mode
// this won't immediately update the widget surface, because we render to an offscreen
// `RenderingContext`.
if new_servo_frame {
state.servo().present();
}
let updated = match (update, &mut self.minibrowser) {
(true, Some(minibrowser)) => minibrowser.update_webview_data(state),
_ => false,
};
// If in headed mode, request a winit redraw event, so we can paint the minibrowser.
if updated || need_window_redraw || new_servo_frame {
if updated || need_window_redraw {
if let Some(window) = window.winit_window() {
window.request_redraw();
}
@ -247,14 +238,7 @@ impl App {
state.shutdown();
self.state = AppState::ShuttingDown;
},
PumpResult::Continue {
new_servo_frame, ..
} => {
if new_servo_frame {
// In headless mode, we present directly.
state.servo().present();
}
},
PumpResult::Continue { .. } => state.repaint_servo_if_necessary(),
}
!matches!(self.state, AppState::ShuttingDown)

View file

@ -73,8 +73,9 @@ pub struct RunningAppStateInner {
/// Whether or not the application interface needs to be updated.
need_update: bool,
/// Whether or not the application needs to be redrawn.
new_servo_frame_ready: bool,
/// Whether or not Servo needs to repaint its display. Currently this is global
/// because every `WebView` shares a `RenderingContext`.
need_repaint: bool,
}
impl Drop for RunningAppState {
@ -101,7 +102,7 @@ impl RunningAppState {
window,
gamepad_support: GamepadSupport::maybe_new(),
need_update: false,
new_servo_frame_ready: false,
need_repaint: false,
}),
}
}
@ -124,6 +125,19 @@ impl RunningAppState {
&self.servo
}
pub(crate) fn repaint_servo_if_necessary(&self) {
if !self.inner().need_repaint {
return;
}
let Some(webview) = self.focused_webview() else {
return;
};
webview.paint();
self.inner().window.rendering_context().present();
self.inner_mut().need_repaint = false;
}
/// Spins the internal application event loop.
///
/// - Notifies Servo about incoming gamepad events
@ -138,15 +152,12 @@ impl RunningAppState {
}
// Delegate handlers may have asked us to present or update compositor contents.
let new_servo_frame = std::mem::replace(&mut self.inner_mut().new_servo_frame_ready, false);
let need_update = std::mem::replace(&mut self.inner_mut().need_update, false);
// Currently, egui-file-dialog dialogs need to be constantly redrawn or animations aren't fluid.
let need_window_redraw = new_servo_frame || self.has_active_dialog();
let need_window_redraw = self.inner().need_repaint || self.has_active_dialog();
let need_update = std::mem::replace(&mut self.inner_mut().need_update, false);
PumpResult::Continue {
need_update,
new_servo_frame,
need_window_redraw,
}
}
@ -484,7 +495,7 @@ impl WebViewDelegate for RunningAppState {
}
fn notify_new_frame_ready(&self, _webview: servo::WebView) {
self.inner_mut().new_servo_frame_ready = true;
self.inner_mut().need_repaint = true;
}
fn play_gamepad_haptic_effect(

View file

@ -403,6 +403,8 @@ impl Minibrowser {
);
}
state.repaint_servo_if_necessary();
if let Some(render_to_parent) = rendering_context.render_to_parent_callback() {
ui.painter().add(PaintCallback {
rect,

View file

@ -680,8 +680,8 @@ impl RunningAppState {
pub fn present_if_needed(&self) {
if self.inner().need_present {
self.inner_mut().need_present = false;
self.active_webview().paint_immediately();
self.servo.present();
self.active_webview().paint();
self.rendering_context.present();
}
}
}