From e57285fe3971404c041f13c80db1ec97adb7b493 Mon Sep 17 00:00:00 2001 From: Marco Mihai Condrache <52580954+marcocondrache@users.noreply.github.com> Date: Mon, 5 Jan 2026 09:30:46 +0100 Subject: [PATCH] gpui: Prepaint Div children with image cache (#46039) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #39914 I was able to reproduce the issue on macOS using the same README. The root cause was that `markdown_preview_view` attaches the image cache to the root div, and while Div correctly applies that cache during request_layout and paint, the prepaint phase didn’t have the same context. Markdown is rendered through a List, and `List::prepaint` renders its items using `layout_as_root`. That meant images were getting loaded during prepaint without the image cache in place. When that happened, Img fell back to the global asset loader, which retains assets indefinitely unless explicitly cleaned up. So every image was loaded twice: - once during layout/paint via RetainAllImageCache (correctly released when the preview closed) - once during prepaint via the global asset system (never released) The result was doubled memory usage and a leak, since the globally loaded images stuck around after the preview was closed. Release Notes: - Fixed a memory leak when previewing markdown files with images Signed-off-by: Marco Mihai Condrache <52580954+marcocondrache@users.noreply.github.com> --- crates/gpui/src/elements/div.rs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index 952cef7f58e6628c71b517ff74735b092edc37f7..8d75b23843be6f0193ef39053d6f7c98ceb1900f 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -1425,6 +1425,11 @@ impl Element for Div { window: &mut Window, cx: &mut App, ) -> Option { + let image_cache = self + .image_cache + .as_mut() + .map(|provider| provider.provide(window, cx)); + let has_prepaint_listener = self.prepaint_listener.is_some(); let mut children_bounds = Vec::with_capacity(if has_prepaint_listener { request_layout.child_layout_ids.len() @@ -1479,16 +1484,18 @@ impl Element for Div { return hitbox; } - window.with_element_offset(scroll_offset, |window| { - for child in &mut self.children { - child.prepaint(window, cx); + window.with_image_cache(image_cache, |window| { + window.with_element_offset(scroll_offset, |window| { + for child in &mut self.children { + child.prepaint(window, cx); + } + }); + + if let Some(listener) = self.prepaint_listener.as_ref() { + listener(children_bounds, window, cx); } }); - if let Some(listener) = self.prepaint_listener.as_ref() { - listener(children_bounds, window, cx); - } - hitbox }, )