web browser (Servo)
git clone https://git.lucas.co/cce-browser.git
The page belongs to a renderer, not to the process
An image id names an entry in one renderer's image table, and a
renderer does not outlive its session: cce-ui's window_runner repairs a
lost Wayland transport by opening a new session around the same
Application, which rebuilds the VkRenderer and with it the image table.
A draw for an id that table does not hold is skipped rather than
reported.
Each Tab holds such an id for its rendered page. Dropping them is the
easy half; the hard half is that nothing would then provoke a new
frame. A page that has finished loading renders once and only paints
again on damage, so `pump` finds no buffer held and the chrome comes
back over a blank window — indefinitely, until the user scrolls or
navigates. (A page that animates would have healed itself, since
`update_pixels` recreates an image under an id the new table lacks.
That is exactly the kind of bug that looks fixed on whatever page you
happen to test with.)
So `renderer_replaced` frees every tab's id and then remaps the active
view, which is the nudge `activate` already relies on to get a frame
out of a tab being switched to. A buffer still held from the old
session is deliberately kept: its pixels are fine, and the next pump
uploads them under a fresh id.
Both backends get it, but they are not equally verified. The WPE path
is the default build, compiles, and is what was reasoned through here.
The Servo path (--features servo) takes the same shape through
`paint_active`, its own repaint primitive — written by hand and NOT
compiled, because building that backend pulls Servo from crates.io and
costs more than the rest of the workspace put together. Whoever next
builds it should expect to fix this up if it is wrong.
Not on the first renderer: no page has rendered yet, and remapping the
view before the first frame only makes the engine repeat work.
window_runner documents the contract above `run`.
src/main.rs | 22 ++++++++++++++++++++++
src/webview.rs | 19 +++++++++++++++++++
src/wpe/host.rs | 37 +++++++++++++++++++++++++++++++++++++
3 files changed, 78 insertions(+)
diff --git a/src/main.rs b/src/main.rs
index 50c8f9c..d512afd 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -434,6 +434,10 @@ pub enum Message {
struct BrowserApp {
host: Host,
+ /// Whether a renderer has been handed over yet — the first one is the
+ /// process's own, any later one is a replacement after a reconnect. See
+ /// `renderer_init`.
+ seen_renderer: bool,
/// Loaded from the app config; re-read when the window regains focus.
settings: settings::Settings,
win: (f32, f32),
@@ -2026,6 +2030,7 @@ impl Application for BrowserApp {
let bookmarks = host.bookmarks();
Self {
host,
+ seen_renderer: false,
settings,
win: (1200.0, 800.0),
scale: 1.0,
@@ -2255,6 +2260,23 @@ impl Application for BrowserApp {
self.host.resize(w, h, scale as f32);
}
+ /// Re-paint the page when the renderer is replaced.
+ ///
+ /// The tab images are **renderer** ids, and a renderer does not outlive
+ /// its session — `window_runner` rebuilds it around the same
+ /// `Application` after a lost Wayland transport, and a draw for an
+ /// unknown id is skipped rather than reported. See
+ /// `Host::renderer_replaced` for why dropping the ids is only half of it.
+ ///
+ /// Not on the first renderer: no page has rendered yet, and remapping the
+ /// view before the first frame would only make the engine repeat work.
+ fn renderer_init(&mut self, _renderer: &mut cce_ui::vk::VkRenderer) {
+ if std::mem::replace(&mut self.seen_renderer, true) {
+ log::info!("[browser] renderer replaced; re-painting the page");
+ self.host.renderer_replaced();
+ }
+ }
+
fn handle_pointer_move(&mut self, pos: LogicalPosition, _needs_rebuild: &mut bool) {
self.pointer = (pos.x, pos.y);
#[cfg(feature = "wpe")]
diff --git a/src/webview.rs b/src/webview.rs
index 71687e5..7c629f0 100644
--- a/src/webview.rs
+++ b/src/webview.rs
@@ -553,6 +553,25 @@ impl ServoHost {
}
}
+ /// Re-paint the page into a renderer that has just replaced the one the
+ /// tab images were uploaded to.
+ ///
+ /// An image id belongs to a **renderer**, not to the process: `cce-ui`'s
+ /// `window_runner` repairs a lost Wayland transport by opening a new
+ /// session around the same `Application`, which rebuilds the renderer and
+ /// with it the image table, and a draw for an unknown id is skipped
+ /// silently. `paint_active` is the repaint primitive here — the same one
+ /// `activate` uses to show a switched-to tab immediately — so the page
+ /// comes back without a reload.
+ pub fn renderer_replaced(&mut self) {
+ for tab in &mut self.tabs {
+ if let Some((id, ..)) = tab.image.take() {
+ cce_ui::vk::free_image(id);
+ }
+ }
+ self.paint_active();
+ }
+
/// Spin Servo, sync delegate signals into tabs, and repaint the active
/// tab if it produced a frame. Returns (new frame, any state change).
pub fn pump(&mut self) -> (bool, bool) {
diff --git a/src/wpe/host.rs b/src/wpe/host.rs
index 986e180..d360d6b 100644
--- a/src/wpe/host.rs
+++ b/src/wpe/host.rs
@@ -777,6 +777,43 @@ impl WebKitHost {
(true, true)
}
+ /// Re-paint the page into a renderer that has just replaced the one the
+ /// tab images were uploaded to.
+ ///
+ /// An image id belongs to a **renderer**, not to the process: `cce-ui`'s
+ /// `window_runner` repairs a lost Wayland transport by opening a new
+ /// session around the same `Application`, which rebuilds the renderer and
+ /// with it the image table. A draw for an unknown id is skipped rather
+ /// than reported, so the chrome came back over an empty page.
+ ///
+ /// Two halves. Dropping the ids is the easy one. The hard one is that
+ /// nothing would otherwise provoke a new frame: a page that has finished
+ /// loading renders once and then only on damage, so `pump` would find no
+ /// buffer held and the window would sit blank until the user scrolled or
+ /// navigated. Remapping the active view is the nudge — it is what
+ /// `activate` already relies on to get a frame out of a tab being
+ /// switched to.
+ ///
+ /// A buffer still held from the old session is deliberately kept: its
+ /// pixels are fine, and the next `pump` uploads them under a fresh id.
+ pub fn renderer_replaced(&mut self) {
+ for tab in &mut self.tabs {
+ if let Some((id, ..)) = tab.image.take() {
+ // A free for an id the new renderer never had is a no-op, and
+ // ids are process-unique, so this cannot reach a live image.
+ cce_ui::vk::free_image(id);
+ }
+ }
+ unsafe {
+ let view = self.active_tab().view;
+ wpe_view_unmap(view);
+ wpe_view_set_visible(view, 1);
+ wpe_view_map(view);
+ let (lw, lh) = self.logical_size();
+ wpe_view_resized(view, lw, lh);
+ }
+ }
+
/// The chrome drew: whatever was uploaded is on screen, so the next
/// engine frame is worth reading. Called from `display_list`.
pub fn frame_drawn(&self) {